Initial import!

This commit is contained in:
markpollack
2008-05-30 22:55:02 +00:00
commit c478a783c0
2978 changed files with 510966 additions and 0 deletions

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1003 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 598 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

2307
doc/reference/src/ado.xml Normal file

File diff suppressed because it is too large Load Diff

154
doc/reference/src/ajax.xml Normal file
View File

@@ -0,0 +1,154 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-->
<chapter id="ajax">
<title>ASP.NET AJAX</title>
<sect1 id="introduction-ajax">
<title>Introduction</title>
<para>Spring's ASP.NET AJAX integration allows for a plain .NET object
(PONO), that is one that doesn't have any attributes or special base
classes, to be exported as a web service, configured via dependency
injection, 'decorated' by applying AOP, and then exposed to client side
JavaScript.</para>
</sect1>
<sect1 id="webServices">
<title>Web Services</title>
<para>Spring.NET, and particularly Spring.Web, improved <ulink
url="http://www.springframework.net/doc-latest/reference/html/webservices.html">support
for web services</ulink> in .NET with the
<classname>WebServiceExporter</classname>. Exporting of an ordinary plain
.NET object as a web service is achieved by registering a custom
implementation of the <classname>WebServiceHandlerFactory</classname>
class as the HTTP handler for <literal>*.asmx</literal> requests.</para>
<para><ulink
url="http://www.springframework.net/doc-latest/reference/html/webservices.html">Microsoft
ASP.NET AJAX</ulink> introduced a new HTTP handler
<classname>System.Web.Script.Services.ScriptHandlerFactory</classname> to
allow a Web Service to be invoked from the browser by using
JavaScript.</para>
<para>Spring's integration allows for both Spring.Web and ASP.NET AJAX
functionality to be used together by creating a new HTTP handler.</para>
<sect2 id="exposingWebServices">
<title>Exposing Web Services</title>
<para>The <classname>WebServiceExporter</classname> combined with the
new HTTP handler exposes PONOs as Web Services in your ASP.NET AJAX
application.</para>
<para>In order for a Web service to be accessed from script, the
<classname>WebServiceExporter</classname> should decorate the Web
Service class with the <classname>ScriptServiceAttribute</classname>.
The code below is taken from the sample application
Spring.Web.Extensions.Sample, aka the 'AJAX' shortcut in the
installation. : <programlisting>
&lt;object id="ContactWebService" type="Spring.Web.Services.WebServiceExporter, Spring.Web"&gt;
&lt;property name="TargetName" value="ContactService"/&gt;
&lt;property name="Namespace" value="http://Spring.Examples.Atlas/ContactService"/&gt;
&lt;property name="Description" value="Contact Web Services"/&gt;
&lt;property name="TypeAttributes"&gt;
&lt;list&gt;
&lt;object type="System.Web.Script.Services.ScriptServiceAttribute, System.Web.Extensions"/&gt;
&lt;/list&gt;
&lt;/property&gt;
&lt;/object&gt;
</programlisting></para>
<para>All that one needs to do in order to use the
<classname>WebServiceExporter</classname> is:</para>
<para><emphasis> 1. Configure the Web.config file of your ASP.NET AJAX
application as a Spring.Web application. </emphasis> <programlisting>
&lt;sectionGroup name="spring"&gt;
&lt;section name="context" type="Spring.Context.Support.WebContextHandler, Spring.Web"/&gt;
&lt;/sectionGroup&gt;
</programlisting> <programlisting>
&lt;spring&gt;
&lt;context&gt;
&lt;resource uri="~/Spring.config"/&gt;
&lt;/context&gt;
&lt;/spring&gt;
</programlisting></para>
<para><emphasis> 2. Register the HTTP handler and the Spring HttpModule
under the <literal>system.web</literal> section. </emphasis>
<programlisting>
&lt;httpHandlers&gt;
&lt;remove verb="*" path="*.asmx"/&gt;
&lt;add verb="*" path="*.asmx" validate="false" type="Spring.Web.Script.Services.ScriptHandlerFactory, Spring.Web.Extensions"/&gt;
&lt;add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/&gt;
&lt;add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false"/&gt;
&lt;/httpHandlers&gt;
&lt;httpModules&gt;
&lt;add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/&gt;
&lt;add name="SpringModule" type="Spring.Context.Support.WebSupportModule, Spring.Web"/&gt;
&lt;/httpModules&gt;
</programlisting></para>
<para><emphasis> 3. Register the HTTP handler and the Spring HttpModule
under <literal>system.webServer</literal> section. </emphasis>
<programlisting>
&lt;modules&gt;
&lt;add name="ScriptModule" preCondition="integratedMode" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/&gt;
&lt;add name="SpringModule" type="Spring.Context.Support.WebSupportModule, Spring.Web"/&gt;
&lt;/modules&gt;
&lt;handlers&gt;
&lt;remove name="WebServiceHandlerFactory-Integrated" /&gt;
&lt;add name="ScriptHandlerFactory" verb="*" path="*.asmx" preCondition="integratedMode"
type="Spring.Web.Script.Services.ScriptHandlerFactory, Spring.Web.Extensions"/&gt;
&lt;add name="ScriptHandlerFactoryAppServices" verb="*" path="*_AppService.axd" preCondition="integratedMode"
type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/&gt;
&lt;add name="ScriptResource" preCondition="integratedMode" verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /&gt;
&lt;/handlers&gt;
</programlisting></para>
<para>You can find a full Web.config file in the example that comes with
this integration.</para>
</sect2>
<sect2 id="callingWebServices">
<title>Calling Web Services by using JavaScript</title>
<para>A proxy class is generated for each Web Service. Calls to Web
Services methods are made by using this proxy class. When using the
<classname>WebServiceExporter</classname>, the name of the proxy class
is equal to the <classname>WebServiceExporter</classname>'s id.
<programlisting>
// This function calls the Contact Web service method
// passing simple type parameters and the callback function
function GetEmails(prefix, count)
{
ContactWebService.GetEmails(prefix, count, GetEmailsOnSucceeded);
}
</programlisting></para>
</sect2>
</sect1>
</chapter>

View File

@@ -0,0 +1,733 @@
<?xml version="1.0" encoding="UTF-8"?>
<chapter id="aop-aspect-library">
<title>Aspect Library</title>
<sect1 id="aop-library-introduction">
<title>Introduction</title>
<para>Spring provides several aspects in the distribution. The most
popular of which is transactional advice, located in the Spring.Data
module. However, the aspects that are documented in this section are those
contained within the Spring.Aop module itself. The aspects in within
Spring.Aop.dll are Caching, Exception Handling, Logging, Retry, and
Parameter Validation. Other traditional advice types such as validation,
security, and thread management, will be included in a future
release.</para>
</sect1>
<sect1 id="caching-aspect">
<title>Caching</title>
<para>Caching the return value of a method or the value of a method
parameter is a common approach to increase application performance.
Application performance is increased with effective use of caching since
layers in the application that are closer to the user can return
information within their own layer as compared to making more expensive
calls to retrieve that information from a lower, and more slow, layer such
as a database or a web service. Caching also can help in terms of
application scalability, which is generally the more important
concern.</para>
<para>The caching support in Spring.NET consists of base cache interfaces
that can be used to specify a specific storage implementation of the cache
and also an aspect that determines where to apply the caching
functionality and its configuration.</para>
<para>The base cache interface that any cache implementation should
implement is <classname>Spring.Caching.ICache</classname> located in
<classname>Spring.Core.</classname> Two implementations are provided,
<classname>Spring.Caching.AspNetCache </classname>located in
<classname>Spring.Web</classname> which stores cache entries within an
ASP.NET cache and a simple implementation,
<classname>Spring.Caching.NonExpiringCache</classname> that stores cache
entries in memory and never expires these entries. Custom implementations
based on 3rd party implementations, such as Oracle Coherence, or
memcached, can be used by implementing the <literal>ICache</literal>
interface.</para>
<para>The cache aspect is
<literal>Spring.Aspects.Cache.CacheAspect</literal> located in
<literal>Spring.Aop</literal>. It consists of three pieces of
functionality, the ability to cache return values, method parameters, and
explicit eviction of an item from the cache. The aspect currently relies
on using attributes to specify the pointcut as well as the behavior, much
like the transactional aspect. Future versions will allow for external
configuration of the behavior so you can apply caching to a code base
without needing to use attributes in the code.</para>
<para>The following attributes are available</para>
<itemizedlist>
<listitem>
<para><literal>CacheResult - used to cache the return
value</literal></para>
</listitem>
<listitem>
<para><literal>CacheResultItems - used when returning a collection as
a return value </literal></para>
</listitem>
<listitem>
<para><literal>CacheParameter - used to cache a method
parameter</literal></para>
</listitem>
<listitem>
<para><literal>InvalidateCache</literal> - used to indicate one or
more cache items should be invalidated.</para>
</listitem>
</itemizedlist>
<para>Each <classname>CacheResult</classname>,
<classname>CacheResultItems</classname>, and
<classname>CacheParameter</classname> attributes define the following
properties.</para>
<itemizedlist>
<listitem>
<para><literal>CacheName</literal> - the name of the cache
implementation to use</para>
</listitem>
<listitem>
<para><literal>Key</literal> - a string representing a Spring
Expression Language (SpEL) expression used as the key in the
cache.</para>
</listitem>
<listitem>
<para><literal>Condition</literal> - a SpEL expression that should be
evaluated in order to determine whether the item should be
cached.</para>
</listitem>
<listitem>
<para><literal>TimeToLive</literal> - The amount of time an object
should remain in the cache (in seconds).</para>
</listitem>
</itemizedlist>
<para>The <classname>InvalidateCache</classname> attribute has properties
for the CacheName, the Key as well as the Condition, with the same
meanings as listed previously.</para>
<para>Each <classname>ICache</classname> implementation will have
properties that are specific to a caching technology. In the case of
<classname>AspNetCache</classname>, the two important properties to
configure are:</para>
<itemizedlist>
<listitem>
<para><literal>SlidingExperation</literal> - If this property value is
set to true, every time the marked object is accessed it's TimeToLive
value is reset to its original value</para>
</listitem>
<listitem>
<para><literal>Priority</literal> - the cache item priority
controlling how likely an object is to be removed from an associated
cache when the cache is being purged.</para>
</listitem>
<listitem>
<para><literal>TimeToLive</literal> - The amount of time an object
should remain in the cache (in seconds).</para>
</listitem>
</itemizedlist>
<para>The values of the Priority enumeration are</para>
<itemizedlist>
<listitem>
<para><literal>Low</literal> - low likelihood of deletion when cache
is purged.</para>
</listitem>
<listitem>
<para><literal>Normal</literal> - default priority for deletion when
cache is purged.</para>
</listitem>
<listitem>
<para><literal>High</literal> - high likelihood of deletion when cache
is purged.</para>
</listitem>
<listitem>
<para><literal>NotRemovable</literal> - cache item not deleted when
cache is purged.</para>
</listitem>
</itemizedlist>
<para>An important element of the applying these attributes is the use of
the expression language that allows for calling context information to
drive the caching actions. Here is an example taken from the Spring Air
sample application of the AirportDao implementation that implements an
interface with the method GetAirport(long id).</para>
<programlisting> [CacheResult("AspNetCache", "'Airport.Id=' + #id", TimeToLive = "0:1:0")]
public Airport GetAirport(long id)
{
// implementation not shown...
}
</programlisting>
<para>The first parameter is the cache name. The second string parameter
is the cache key and is a string expression that incorporates the argument
passed into the method, the id. The method parameter names are exposed as
variables to the key expression. If you do not specify a key, then all the
parameter values will be used to cache the returned value. The expression
may also call out to other objects in the Spring container allowing for a
more complex key algorithm to be encapsulated. The end result is that the
Airport object is cached by id for 60 seconds in a cache named
AspNetCache. The TimetoLive property could also have been specified on the
configuration of the AspNetCache object.</para>
<para>The configuration to enable the caching aspect is shown below</para>
<programlisting> &lt;object id="CacheAspect" type="Spring.Aspects.Cache.CacheAspect, Spring.Aop"/&gt;
&lt;object id="AspNetCache" type="Spring.Caching.AspNetCache, Spring.Web"&gt;
&lt;property name="SlidingExpiration" value="true"/&gt;
&lt;property name="Priority" value="CachePriority.Low"/&gt;
&lt;property name="TimeToLive" value="00:02:00"/&gt;
&lt;/object&gt;
&lt;!-- Apply aspects to DAOs --&gt;
&lt;object type="Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxyCreator, Spring.Aop"&gt;
&lt;property name="ObjectNames"&gt;
&lt;list&gt;
&lt;value&gt;*Dao&lt;/value&gt;
&lt;/list&gt;
&lt;/property&gt;
&lt;property name="InterceptorNames"&gt;
&lt;list&gt;
&lt;value&gt;CacheAspect&lt;/value&gt;
&lt;/list&gt;
&lt;/property&gt;
&lt;/object&gt;</programlisting>
<para>in this example an <classname>ObjectNameAutoProxyCreator</classname>
was used to apply the cache aspect to objects that have Dao in their name.
The AspNetCache setting for TimeToLive will override the TimeToLive value
set at the method level via the attribute.</para>
</sect1>
<sect1 id="exception-aspect">
<title>Exception Handling</title>
<para>In some cases existing code can be easily adopted to a simple error
handling strategy that can perform one of the following actions</para>
<itemizedlist>
<listitem>
<para>translations - either wrap the thrown exception inside a new one
or replace it with a new exception type (no inner exception is
set).</para>
</listitem>
<listitem>
<para>return value - the exception is ignored and a return value for
the method is provided instead</para>
</listitem>
<listitem>
<para>swallow - the exception is ignored.</para>
</listitem>
</itemizedlist>
<para>The applicability of general exception handling advice depends
greatly on how tangled the code is regarding access to local variables
that may form part of the exception. Once you get familiar with the
feature set of Spring declarative exception handling advice you should
evaluate where it may be effectively applied in your code base. It is
worth noting that you can still chain together multiple pieces of
exception handling advice allowing you to mix the declarative approach
shown in this section with the traditional inheritance based approach,
i.e. implementing IThrowsAdvice or IMethodInterceptor.</para>
<para>Declarative exception handling is expressed in the form of a
mini-language relevant to the domain at hand, exception handling. This
could be referred to as a Domain Specific Language (DSL). Here is a simple
example, which should hopefully be self explanatory.</para>
<para><programlisting>&lt;object name="exceptionHandlingAdvice" type="Spring.Aspects.Exceptions.ExceptionHandlerAdvice, Spring.Aop"&gt;
&lt;property name="exceptionHandlers"&gt;
&lt;list&gt;
&lt;value&gt;<emphasis role="bold">on exception name ArithmeticException wrap System.InvalidOperationException</emphasis>&lt;/value&gt;
&lt;/list&gt;
&lt;/property&gt;
&lt;/object&gt;</programlisting>What this is instructing the advice to do is
the following bit of code when an ArithmeticException is thrown, throw new
System.InvalidOperationException("Wrapped ArithmeticException", e), where
e is the original ArithmeticException. The default message, "Wrapped
ArithmethicException" is automatically appended. You may however specify
the message used in the newly thrown exception as shown below</para>
<programlisting>on exception name ArithmeticException wrap System.InvalidOperationException 'My Message'</programlisting>
<para>Similarly, if you would rather replace the exception, that is do not
nest one inside the other, you can use the following syntax</para>
<programlisting>on exception name ArithmeticException replace System.InvalidOperationException
or
on exception name ArithmeticException replace System.InvalidOperationException 'My Message'</programlisting>
<para>Both wrap and replace are special cases of the more general
translate action. An example of a translate expression is shown
below</para>
<para><programlisting>on exception name ArithmeticException translate new System.InvalidOperationException('My Message, Method Name ' + #method.Name, #e)</programlisting>What
we see here after the translate keyword is text that will be passed into
Spring's expression language (SpEL) for evaluation. Refer to the chapter
on the <link linkend="expression">expression language</link> for more
details. One important feature of the expression evaluation is the
availability of variables relating to the calling context when the
exception was thrown. These are</para>
<itemizedlist>
<listitem>
<para>method - the MethodInfo object corresponding to the method that
threw the exception</para>
</listitem>
<listitem>
<para>args - the argument array to the method that threw the
exception, signature is object[]</para>
</listitem>
<listitem>
<para>target - the AOP target object instance.</para>
</listitem>
<listitem>
<para>e - the thrown exception</para>
</listitem>
</itemizedlist>
<para>You can invoke methods on these variables, prefixed by a '#' in the
expression. This gives you the flexibility to call special purpose
constructors that can have any piece of information accessible via the
above variables, or even other external data through the use of SpEL's
ability to reference objects within the Spring container.</para>
<para>You may also choose to 'swallow' the exception or to return a
specific return value, for example</para>
<programlisting>on exception name ArithmeticException swallow
or
on exception name ArithmeticException return 12</programlisting>
<para>You may also simply log the exception</para>
<programlisting>on exception name ArithmeticException,ArgumentException log 'My Message, Method Name ' + #method.Name</programlisting>
<para>Here we see that a comma delimited list of exception names can be
specified.</para>
<para>The logging is performed using the Commons.Logging library that
provides an abstraction over the underlying logging implementation.
Logging is currently at the debug level with a logger name of
"LogExceptionHandler" The ability to specify these values will be a future
enhancement and likely via a syntax resembling a constructor for the
action, i.e. log(Debug,"LoggerName").</para>
<para>Multiple exception handling statements can be specified within the
&lt;list&gt; shown above. The processing flow is on exception, the name of
the exception listed in the statement is compared to the thrown exception
to see if there is a match. A comma separated list of exceptions can be
used to group together the same action taken for different exception
names. If the action to take is logging, then the logging action is
performed and the search for other matching exception names continues. For
all other actions, namely translate, wrap, replace, swallow, return, once
an exception handler is matched, those in the chain are no longer
evaluated. Note, do not confuse this handler chain with the general advice
AOP advice chain. For translate, wrap, and replace actions a SpEL
expression is created and used to instantiate a new exception (in addition
to any other processing that may occur when evaluating the expression)
which is then thrown.</para>
<para>The exception handling DSL also supports the ability to provide a
SpEL boolean expression to determine if the advice will apply instead of
just filtering by the expression name. For example, the following is the
equivalent to the first example based on exception names but compares the
specific type of the exception thrown</para>
<programlisting><emphasis role="bold">on exception (#e is T(System.ArithmeticException))</emphasis> wrap System.InvalidOperationException</programlisting>
<para>The syntax use is 'on exception (SpEL boolean expression)' and
inside the expression you have access to the variables of the calling
context listed before, i.e. method, args, target, and e. This can be
useful to implement a small amount of conditional logic, such as checking
for a specific error number in an exception, i.e. <literal>(#e is
T(System.Data.SqlException) &amp;&amp; #e.Errors[0].Number in
{156,170,207,208})</literal>, to catch and translate bad grammar codes in
a SqlException.</para>
<para>While the examples given above are toy examples, they could just as
easily be changed to convert your application specific exceptions. If you
find yourself pushing the limits of using SpEL expressions, you will
likely be better off creating your own custom aspect class instead of a
scripting approach.</para>
<sect2>
<title>Language Reference</title>
<para>The general syntax of the language is</para>
<para><literal>on exception name [ExceptionName1,ExceptionName2,...]
[action] [SpEL expression]</literal></para>
<para>or</para>
<para><literal>on exception (SpEL boolean expression) [action] [SpEL
expression]</literal></para>
<para>The exception names are required as well as the action. The valid
actions are</para>
<itemizedlist>
<listitem>
<para>log</para>
</listitem>
<listitem>
<para>translate</para>
</listitem>
<listitem>
<para>wrap</para>
</listitem>
<listitem>
<para>replace</para>
</listitem>
<listitem>
<para>return</para>
</listitem>
<listitem>
<para>swallow</para>
</listitem>
</itemizedlist>
<para>The form of the expression depends on the action. For logging, the
entire string is taken as the SpEL expression to log. Translate expects
an exception to be returned from evaluation the SpEL expression. Wrap
and replace are shorthand for the translate action. For wrap and replace
you specify the exception name and the message to pass into the standard
exception constructors (string, exception) and (string). The exception
name can be a partial or fully qualified name. Spring will attempt to
resolve the typename across all referenced assemblies. You may also
register type aliases for use with SpEL in the standard manner with
Spring.NET and those will be accessible from within the exception
handling expression.</para>
</sect2>
</sect1>
<sect1 id="logging-aspect">
<title>Logging</title>
<para>The logging advice lets you log the information on method entry,
exit and thrown exception (if any). The implementation is based on the
logging library, <link linkend="???">Common.Logging</link>, that provides
portability across different logging libraries. There are a number of
configuration options available, listed below</para>
<itemizedlist>
<listitem>
<para>LogUniqueIdentifier</para>
</listitem>
<listitem>
<para>LogExecutionTime</para>
</listitem>
<listitem>
<para>LogMethodArguments</para>
</listitem>
<listitem>
<para>LogReturnValue</para>
</listitem>
<listitem>
<para>Separator</para>
</listitem>
<listitem>
<para>LogLevel</para>
</listitem>
</itemizedlist>
<para>You declare the logging advice in IoC container with the following
XML fragment. Alternatively, you can use the class
<classname>SimpleLoggingAdvice</classname> programatically.</para>
<programlisting>&lt;object name="loggingAdvice" type="Spring.Aspects.Logging.SimpleLoggingAdvice, Spring.Aop"&gt;
&lt;property name="logUniqueIdentifier" value="true"/&gt;
&lt;property name="logExecutionTime" value="true"/&gt;
&lt;property name="logMethodArguments" value="true"/&gt;
&lt;property name="LogReturnValue" value="true"/&gt;
&lt;property name="Separator" value=";"/&gt;
&lt;property name="LogLevel" value="Info"/&gt;
&lt;property name="HideProxyTypeNames" value="true"/&gt;
&lt;property name="UseDynamicLogger" value="true"/&gt;
&lt;/object&gt;</programlisting>
<para>
The default values for LogUniqueIdentifier, LogExecutionTime, LogMethodArguments and
LogReturnValue are false. The default separator value is ", " and the
default log level is Common.Logging's LogLevel.Trace.</para>
<para>You can set the name of the logger with the property
<property>LoggerName</property>, for example "DataAccessLayer" for a
logging advice that would be applied across the all the classes in the
data access layer. That works well when using a 'category' style of
logging. If you do not set the <property>LoggerName</property> property,
then the type name of the logging advice is used as the logging name.
Another approach to logging is to log based on the type of the object
being called, the target type. Since often this is a proxy class with a
relatively meaningless name, the property
<property>HideProxyTypeNames</property> can be set to true to show the
true target type and not the proxy type.</para>
<para>To further extend the functionality of the
<classname>SimpleLoggingAdvice</classname> you can subclass
<classname>SimpleLoggingAdvice</classname> and override the methods</para>
<itemizedlist>
<listitem>
<para><literal>string GetEntryMessage(IMethodInvocation invocation,
string idString)</literal></para>
</listitem>
<listitem>
<para><literal>string GetExceptionMessage(IMethodInvocation
invocation, Exception e, TimeSpan executionTimeSpan, string
idString)</literal></para>
</listitem>
<listitem>
<para><literal>string GetExitMessage(IMethodInvocation invocation,
object returnValue, TimeSpan executionTimeSpan, string
idString)</literal></para>
</listitem>
</itemizedlist>
<para>The default implementation to calculate a unique identifier is to
use a GUID. You can alter this behavior by overriding the method
<literal>string CreateUniqueIdentifier()</literal>. The
<classname>SimpleLoggingAdvice</classname> class inherits from
<classname>AbstractLoggingAdvice</classname>, which has the abstract
method <literal>object InvokeUnderLog(IMethodInvocation invocation, ILog
log)</literal> and you can also override the method <literal>ILog
GetLoggerForInvocation(IMethodInvocation invocation)</literal> to
customize the logger instance used for logging. Refer to the SDK
documentation for more details on subclassing
<classname>AbstractLoggingAdvice</classname>.</para>
<para>As an example of the Logging advice's output, adding the advice to
the method</para>
<programlisting>public int Bark(string message, int[] luckyNumbers)
{
return 4;
}</programlisting>
<para>And calling Bark("hello", new int[]{1, 2, 3} ), results in the
following output</para>
<programlisting>Entering Bark, 5d2bad47-62cd-435b-8de7-91f12b7f433e, message=hello; luckyNumbers=System.Int32[]
Exiting Bark, 5d2bad47-62cd-435b-8de7-91f12b7f433e, 30453.125 ms, return=4</programlisting>
<para>The method parameters values are obtained using the ToString()
method. If you would like to have an alternate implementation, say to view
some values in an array, override the method string
GetMethodArgumentAsString(IMethodInvocation invocation).</para>
<para>The Spring 1.2 release will have an additional logging advice
implementation that leverages the Spring Expression Language to further
customize the content of the logging messages via simple configuration
using similar syntax to the retry and exception handling advice.</para>
</sect1>
<sect1 id="retry-aspect">
<title>Retry</title>
<para>When making a distributed call it is often a common requirement to
be able to retry the method invocation if there was an exception.
Typically the exception will be due to a communication issue that is
intermittent and retrying over a period of time will likely result in a
successful invocation. When applying retry advice it is important to know
if making two calls to the remote service will cause side effects.
Generally speaking, the method being invoked should be <ulink
url="http://en.wikipedia.org/wiki/Idempotent#Computer_Science">idempotent</ulink>,
that is, it is safe to call multiple times.</para>
<para>The retry advice is specified using a little language, i.e a DSL. A
simple example is shown below</para>
<programlisting>on exception name ArithmeticException retry 3x delay 1s</programlisting>
<para>The meaning is: when an exception that has 'ArithmeticException' in
its type name is thrown, retry the invocation up to 3 times and delay for
1 second between each retry event.</para>
<para>You can also provide a SpEL (Spring Expression Language) expression
that calculates the time interval to sleep between each retry event. The
syntax for this is shown below</para>
<programlisting>on exception name ArithmeticException retry 3x rate (1*#n + 0.5)</programlisting>
<para>As with the exception handling advice, you may also specify a
boolean SpEL that must evaluate to true in order for the advice to apply.
For example</para>
<programlisting>on exception (#e is T(System.ArithmeticException)) retry 3x delay 1s
on exception (#e is T(System.ArithmeticException)) retry 3x rate (1*#n + 0.5)</programlisting>
<para>The time specified after the delay keyword is converted to a
TimeSpan object using Spring's TimeSpanConverter. This supports setting
the time as an integer + time unit. Time units are (d, h, m, s, ms)
representing (days, hours, minutes, seconds, and milliseconds). For
example; 1d = 1day, 5h = 5 hours etc. You can not specify a string such as
'1d 5h'. The value that is calculated from the expression after the rate
keyword is interpreted as a number of seconds. The power of using SpEL for
the rate expression is that you can easily specify some exponential retry
rate (a bigger delay for each retry attempt) or call out to a custom
function developed for this purpose.</para>
<para>When using a SpEL expression for the filter condition or for the
rate expression, the following variable are available</para>
<para><itemizedlist>
<listitem>
<para>method - the MethodInfo object corresponding to the method
that threw the exception</para>
</listitem>
<listitem>
<para>args - the argument array to the method that threw the
exception, signature is object[]</para>
</listitem>
<listitem>
<para>target - the AOP target object instance.</para>
</listitem>
<listitem>
<para>e - the thrown exception</para>
</listitem>
</itemizedlist>You declare the advice in IoC container with the
following XML fragment. Alternatively, you can use the
<classname>RetryAdvice</classname> class programatically.</para>
<programlisting>&lt;object name="exceptionHandlingAdvice" type="Spring.Aspects.RetryAdvice, Spring.Aop"&gt;
&lt;property name="retryExpression" value="<emphasis role="bold">on exception name ArithmeticException retry 3x delay 1s</emphasis>"/&gt;
&lt;/object&gt;</programlisting>
<sect2>
<title>Language Reference</title>
<para>The general syntax of the language is</para>
<para><literal>on exception name [ExceptionName1,ExceptionName2,...]
retry [number of times]x [delay|rate] [delay time|SpEL rate
expression]</literal></para>
<para>or</para>
<para><literal>on exception (SpEL boolean expression) retry [number of
times]x [delay|rate] [delay time|SpELrate expression]</literal></para>
</sect2>
</sect1>
<sect1 id="tx-aspect">
<title>Transactions</title>
<para>The transaction aspect is more fully described in the section on
<link linkend="transaction">transaction management</link>.</para>
</sect1>
<sect1 id="parameter-validation">
<title>Parameter Validation</title>
<para>Spring provides a UI-agnostic <link linkend="validation">validation
framework</link> in which you can declare validation rules, both
progammatically and declaratively, and have those rules evaluated against
an arbitrary .NET object. Spring provides additional support for rendering
of validation errors within Spring's ASP.NET framework. (See the section
on <link linkend="validation-aspnet-usage" os="">ASP.NET usage tips</link>
for more information.) However, validation is not confined to the UI tier.
It is a common task that occurs across most, if not all, applications
layers. Validation that is performed in the UI layer is often repeated in
the service layer, in order to be proactive in case non UI-based clients
invoke the service layer. Validation rules completely different from those
used in the UI layer may be used on the server side.</para>
<para>To address some of the common needs for validation on the server
side, Spring provides parameter validation advice so that applies Spring's
validation rules to the method parameters. The class
<classname>ParameterValidationAdvice</classname> is used in conjunction
with the <classname>Validated</classname> attribute to specify which
validation rules are applied to method parameters. For example, to apply
parameter validation to the method SuggestFlights in the BookingAgent
class used in the <link linkend="springair">SpringAir sample
application</link>, you would apply the <classname>Validated</classname>
attribute to the method parameters as shown below.</para>
<programlisting>public FlightSuggestions SuggestFlights( [Validated("tripValidator")] Trip trip)
{
// unmodified implementation goes here
}</programlisting>
<para>The <literal>Validated</literal> attribute takes a string name that
specifies the name of the validation rule, i.e. the name of the IValidator
object in the Spring application context. The
<classname>Validated</classname> attribute is located in the namespace
<literal>Spring.Validation</literal> of the <literal>Spring.Core</literal>
assembly.</para>
<para>The configuration of the advice is to simply define the an instance
of the <literal>ParameterValidationAdvice</literal> class and apply the
advice, for example based on object names using an
<classname>ObjectNameAutoProxyCreator</classname>, as shown below,</para>
<programlisting> &lt;object id="<emphasis role="bold">validationAdvice</emphasis>" type="Spring.Aspects.Validation.ParameterValidationAdvice, Spring.Aop"/&gt;
&lt;object type="Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxyCreator, Spring.Aop"&gt;
&lt;property name="ObjectNames"&gt;
&lt;list&gt;
&lt;value&gt;bookingAgent&lt;/value&gt;
&lt;/list&gt;
&lt;/property&gt;
&lt;property name="InterceptorNames"&gt;
&lt;list&gt;
&lt;value&gt;<emphasis role="bold">validationAdvice</emphasis>&lt;/value&gt;
&lt;/list&gt;
&lt;/property&gt;
&lt;/object&gt;</programlisting>
<para>When the advised method is invoked first the validation of each
method parameter is performed. If all validation succeeds, then the method
body is executed. If validation fails an exception of the type
<classname>ValidationException</classname> is thrown and you can retrieve
errors information from its property <literal>ValidationErrors</literal>.
See the SDK documentation for details.</para>
</sect1>
</chapter>

File diff suppressed because it is too large Load Diff

2470
doc/reference/src/aop.xml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<chapter id="background">
<title>Background information</title>
<sect1 id="background-ioc">
<title>Inversion of Control</title>
<para>In early 2004, Martin Fowler asked the readers of his site: when
talking about Inversion of Control: <emphasis>"the question, is what
aspect of control are they inverting?"</emphasis>. After talking about the
term Inversion of Control Martin suggests renaming the pattern, or at
least giving it a more self-explanatory name, and starts to use the term
<emphasis>Dependency Injection</emphasis>. His <ulink
url="http://martinfowler.com/articles/injection.html">article</ulink>
continues to explain some of the ideas behind this important software
engineering principle.</para>
<para>Other references you may find useful are</para>
<itemizedlist>
<listitem>
<para>Wikipedia Article - <ulink
url="http://en.wikipedia.org/wiki/Dependency_injection">Dependency
Injection</ulink></para>
</listitem>
<listitem>
<para>CodeProject article - <ulink
url="http://www.codeproject.com/cs/design/DependencyInjection.asp">Dependency
Injection for Loose Coupling</ulink></para>
</listitem>
</itemizedlist>
</sect1>
</chapter>

325
doc/reference/src/dao.xml Normal file
View File

@@ -0,0 +1,325 @@
<?xml version="1.0" encoding="UTF-8"?>
<chapter id="dao">
<title>DAO support</title>
<section id="dao-introduction">
<title>Introduction</title>
<para>Spring promotes the use of data access interfaces in your
application architecture. These interfaces encapsulate the storage and
retrieval of data and objects specific to your business domain without
reference to a specific persistence API. Within a layered architecture,
the service layer is typically responsible for coordinating responses to a
particular business request and it delegates any persistence related
activities to objects that implement these data access interfaces. These
objects are commonly referred to as DAOs (Data Access Objects) and the
architectural layer as a DAL (Data Access Layer).</para>
<para>The benefits of using DAOs in your application are increased
portability across persistence technology and ease of testing. Testing is
more easily facilitated because a mock or stub implementation of the data
access interface can be easily created in a NUnit test so that service
layer functionality can be tested without any dependency on the database.
This is beneficial because tests that rely on the database are usually
hard to set up and tear down and also are impractical for testing
exceptional behavior.</para>
<para>The Data Access Object (DAO) support in Spring is aimed at making it
easy to work with data access technologies like ADO.NET and NHibernate in
a standardized way. Spring provides two central pieces of functionality to
meet this goal. The first is providing a common exception hierarchy across
providers and the second is providing base DAOs classes that raise the
level of abstraction when performing common ADO.NET operations. This
allows one to switch between the aforementioned persistence technologies
fairly easily and it also allows one to code without worrying about
catching exceptions that are specific to each technology.</para>
</section>
<section id="dao-exceptions">
<title>Consistent exception hierarchy</title>
<para>Database exceptions in the ADO.NET API are not consistent across
providers. The .NET 1.1 BCL did not provide a common base class for
ADO.NET exceptions. As such you were required to handle exceptions
specific to each provider such as
<classname>System.Data.SqlClient.SqlException</classname> or
<classname>System.Data.OracleClient.OracleException</classname>. The .NET
2.0 BCL improved in this regard by introducing a common base class for
exceptions, <classname>System.Data.Common.DbException</classname>. However
the common DbException is not very portable either as it provides a vendor
specific error code as the underlying piece of information as to what went
wrong. This error code is different across providers for the same
conceptual error, such as a violation of data integrity or providing bad
SQL grammar.</para>
<para>To promote writing portable and descriptive exception handling code
Spring provides a convenient translation from technology specific
exceptions like <classname>System.Data.SqlClient.SqlException</classname>
or <classname>System.Data.OracleClient.OracleException</classname> to its
own exception hierarchy with the
<classname>Spring.Dao.DataAccessException</classname> as the root
exception. These exceptions wrap the original exception so there is never
any risk that one might lose any information as to what might have gone
wrong.</para>
<para>In addition to exceptions from ADO.NET providers, Spring can also
wrap NHibernate-specific exceptions.. This allows one to handle most
persistence exceptions, which are non-recoverable, only in the appropriate
layers, without boilerplate using or catch and throw blocks, and exception
declarations. As mentioned above, ADO.NET exceptions (including
database-specific dialects) are also converted to the same hierarchy,
meaning that one can perform some operations with ADO.NET within a
consistent programming model. The above holds true for the various
template-based versions of the ORM access framework.</para>
<para>The exception hierarchy that Spring uses is outlined in the
following image:</para>
<mediaobject>
<imageobject>
<imagedata fileref="images/DataAccessException.gif" />
</imageobject>
</mediaobject>
<para>(Please note that the class hierarchy detailed in the above image
shows only a subset of the whole, rich,
<classname>DataAccessException</classname> hierarchy.)</para>
<para>The exception translation functionality is in the namespace
Spring.Data.Support and is based on the interface
<literal>IAdoExceptionTranslator</literal> shown below.</para>
<programlisting>public interface IAdoExceptionTranslator
{
DataAccessException Translate( string task, string sql, Exception exception );
}</programlisting>
<para>The arguments to the translator are a task string providing a
description of the task being attempted, the SQL query or update that
caused the problem, and the 'raw' exception thrown by the ADO.NET data
provider. The additional task and SQL arguments allow for very readable
and clear error messages to be created when an exception occurs.</para>
<para>A default implementation,
<literal>ErrorCodeExceptionTranslator</literal>, is provided that uses the
error codes defined for each data provider in the file dbproviders.xml.
Refer to this file, an embedded resource in the Spring.Data assembly, for
the exact mappings of error codes to Spring DataAccessExceptions.</para>
<para>A common need is to modify the error codes that are map onto the
exception hierarchy. There are several ways to accomplish this
task.</para>
<para>One approach is to override the error codes that are defined in
<code>assembly://Spring.Data/Spring.Data.Common/dbproviders.xml</code>. By
default, the <link
linkend="dbprovider-dbprovider">DbProviderFactory</link> will look for
additional metadata for the IoC container it uses internally to define and
manage the DbProviders in a file named <literal>dbProviders.xml</literal>
located in the root runtime directory. (You can change this location, see
the documentation on <link lang=""
linkend="dbprovider-dbprovider">DbProvider</link> for more information.)
This is a standard Spring application context so all features, such as
<link
linkend="objects-factory-customizing-factory-postprocessors">ObjectFactoryPostProcessors</link>
are available and will be automatically applied. Defining a <link
linkend="objects-factory-overrideconfigurer">PropertyOverrideConfigurer</link>
in this additional configuration file will allow for you to override
specific property values defined in the embedded resource file. As an
example, the additional <literal>dbProviders.xml</literal> file shown
below will add the error code <literal>2601</literal> to the list of error
codes that map to a
<literal>DataIntegrityViolationException</literal>.</para>
<para><programlisting>&lt;objects xmlns='http://www.springframework.net'&gt;
&lt;alias name='SqlServer-2.0' alias='SqlServer2005'/&gt;
&lt;object name="appConfigPropertyOverride" type="Spring.Objects.Factory.Config.PropertyOverrideConfigurer, Spring.Core"&gt;
&lt;property name="Properties"&gt;
&lt;name-values&gt;
&lt;add key="SqlServer2005.DbMetadata.ErrorCodes.DataIntegrityViolationCodes"
value="544,2601,2627,8114,8115"/&gt;
&lt;/name-values&gt;
&lt;/property&gt;
&lt;/object&gt;
&lt;/objects&gt;</programlisting>The reason to define the alias is that <link
linkend="objects-factory-overrideconfigurer">PropertyOverrideConfigurer</link>
assumes a period <literal>(.)</literal> as the separator to pick out the
object name but the names of the objects in
<literal>dbProviders.xml</literal> have periods in them (i.e.
SqlServer-2.0 or System.Data.SqlClient). Creating an alias that has no
periods in the name is a workaround.</para>
<para>Another way to customize the mappings of error codes to exceptions
is to subclass <classname>ErrorCodeExceptionTranslator</classname> and
override the method, <literal>DataAccessException
TranslateException(string task, string sql, string errorCode, Exception
exception)</literal>. This will be called before referencing the metadata
to perform exception translation. The vendor specific error code provided
as a method argument has already been parsed out of the raw ADO.NET
exception. If you create your own specific subclass, then you should set
the property <literal>ExceptionTranslator</literal> on
<literal>AdoTemplate</literal> and
<literal>HibernateTemplate/HibernateTransactionManager</literal> to refer
to your custom implementation (unless you are using autowiring).</para>
<para>The third way is to write an implementation of
<literal>IAdoExceptionTranslator</literal> and set the property
<literal>FallbackTranslator</literal>'on
<literal>ErrorCodeExceptionTranslator</literal>. In this case you are
responsible for parsing our the vendor specific error code from the raw
ADO.NET exception. As with the case of subclassing
ErrorCodeExceptionTranslator, you will need to refer to this custom
exception translator when using <literal>AdoTemplate</literal> or
<literal>HibernateTemplate/HibernateTransactionManager</literal>.</para>
<para>The ordering of the exception translation processing is as follows.
The method TranslateException is called first, then the standard exception
translation logic, then the FallbackTranslator.</para>
<para>Note that you can use this API directly in your own Spring
independent data layer. If you are using Spring's ADO.NET abstraction
class, <literal>AdoTemplate</literal>, or
<literal>HibernateTemplate</literal>, the converted exceptions will be
thrown automatically. Somewhere in between these two cases is using
Spring's declarative transaction management features in .NET 2.0 with the
raw ADO.NET APIs and using <literal>IAdoExceptionTranslator</literal> in
your exception handling layer (which might be implemented in AOP using
Spring's exception translation aspect).</para>
<para>Some of the more common data access exceptions are described here.
Please refer to the API documentation for more details.</para>
<table>
<title>Common DataAccessExceptions</title>
<tgroup cols="2">
<colspec align="left" />
<thead>
<row>
<entry>Exception</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>BadSqlGrammarException</entry>
<entry>Exception thrown when SQL specified is invalid.</entry>
</row>
<row>
<entry>DataIntegrityViolationException</entry>
<entry>Exception thrown when an attempt to insert or update data
results in violation of an integrity constraint. For example,
inserting a duplicate key.</entry>
</row>
<row>
<entry>PermissionDeniedDataAccessException</entry>
<entry>Exception thrown when the underling resource denied a
permission to access a specific element, such as a specific
database table.</entry>
</row>
<row>
<entry>DataAccessResourceFailureException</entry>
<entry>Exception thrown when a resource fails completely, for
example, if we can't connect to a database.</entry>
</row>
<row>
<entry>ConcurrentyFailureException</entry>
<entry>Exception thrown when a concurrency error occurs.
OptimisticLockingFailureException and
PessimisticLockingFailureException are subclasses. This is a
useful exception to catch and to retry the transaction again. See
Spring's <link linkend="retry-aspect">Retry Aspect</link> for an
AOP based solution.</entry>
</row>
<row>
<entry>OptimisticLockingFailureException</entry>
<entry>Exception thrown when there an optimistic locking failure
occurs. The subclass ObjectOptimisticLockingFailureException can
be used to examine the Type and the IDof the object that failed
the optimistic locking.</entry>
</row>
<row>
<entry>PessimisticLockingFailure</entry>
<entry>Exception thrown when a pessimistic locking failure
occures. Subclasses of this exception are
CannotAcquireLockException, CannotSerializeTransactionException,
and DeadlockLoserDataAccessException.</entry>
</row>
<row>
<entry>CannotAcquireLockException</entry>
<entry>Exception thrown when a lock can not be acquired, for
example during an update, i..e a select for update</entry>
</row>
<row>
<entry>CannotSerializeTransactionException</entry>
<entry>Exception thrown when a transaction can not be
serialized.</entry>
</row>
</tbody>
</tgroup>
</table>
</section>
<section>
<title>Consistent abstract classes for DAO support</title>
<para>To make it easier to work with a variety of data access technologies
such as ADO.NET, NHibernate, and iBatis.NET in a consistent way, Spring
provides a set of abstract DAO classes that one can extend. These abstract
classes have methods for providing the data source and any other
configuration settings that are specific to the technology one is
currently using.</para>
<para>DAO support classes:</para>
<itemizedlist>
<listitem>
<para><classname>AdoDaoSupport</classname> - super class for ADO.NET
data access objects. Requires a
<interfacename>DbProvider</interfacename> to be provided; in turn,
this class provides a <classname>AdoTemplate</classname> instance
initialized from the supplied
<interfacename>DbProvider</interfacename> to subclasses. See the
documentation for <literal>AdoTemplate</literal> for more
information.</para>
</listitem>
<listitem>
<para><classname>HibernateDaoSupport</classname> - super class for
NHibernate data access objects. Requires a
<interfacename>ISessionFactory</interfacename> to be provided; in
turn, this class provides a <classname>HibernateTemplate</classname>
instance initialized from the supplied
<interfacename>SessionFactory</interfacename> to subclasses. Can
alternatively be initialized directly via a
<classname>HibernateTemplate</classname>, to reuse the latter's
settings like <interfacename>SessionFactory</interfacename>, flush
mode, exception translator, etc. This is contained in a download
separate from the main Spring.NET distribution.</para>
</listitem>
</itemizedlist>
</section>
</chapter>

View File

@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="UTF-8"?>
<chapter id="data-quickstart">
<title>Data Access QuickStart</title>
<section>
<title>Introduction</title>
<para>The data access quick start demonstrates the API usage of
AdoTemplate (both generic and non-generic versions) as well as the use of
the object based data access classes contained in Spring.Data.Objects. It
uses the Northwind database and is located under the directory
examples/DataAccessQuickStart.</para>
<para>The quick start contains pseudo DAO objects and a collection of
NUnit tests to exercise them rather than a full blown application. To run
the tests from within VS.NET install <ulink
url="http://www.testdriven.net/"><link
linkend="???">TestDriven.NET</link></ulink>, <ulink
url="http://www.jetbrains.com/resharper/">ReSharper</ulink>, or an
equivalent . The listing of DAO classes and the parts of Spring.Data that
they demonstrate is shown below.</para>
<itemizedlist>
<listitem>
<para><classname>CommandCallbackDao</classname> - Use of the
ICommandCallback and CommandCallbackDelegate</para>
</listitem>
<listitem>
<para><classname>ResultSetExtractorDao</classname> - Use of
IResultSetExtractor and ResultSetExtractorDelegate</para>
</listitem>
<listitem>
<para><classname>RowCallbackDao</classname> - Use of IRowCallback and
RowCallbackDelegate</para>
</listitem>
<listitem>
<para><classname>RowMapperDao</classname> - Use of IRowMapper and
RowMapperDelegate</para>
</listitem>
<listitem>
<para><classname>QueryForObject</classname> - Use of QueryForObject
method.</para>
</listitem>
<listitem>
<para><classname>StoredProcDao</classname> - Use of
Spring.Data.Objects.StoredProcedure</para>
</listitem>
</itemizedlist>
<para>The are simple domain objects in the Spring.DataQuickStart.Domain
namespace, collections of which are generally returned from the DAO
methods.</para>
<section>
<title>Database configuration</title>
<para>To get started running the 'unit test' you should configure the
database connection string. The listing in
DataQuickStart.GenericTemplate.ExampleTests.xml is shown below</para>
<programlisting>&lt;objects xmlns="http://www.springframework.net"
xmlns:db="http://www.springframework.net/database"&gt;
&lt;db:provider id="dbProvider"
provider="SqlServer-1.1"
connectionString="Data Source=(local);Database=Northwind;User ID=springqa;Password=springqa;Trusted_Connection=False"/&gt;
&lt;! -- other definitions not shown
&lt;/objects&gt;</programlisting>
<para>You should change the value of the provider element to correspond
to you database and the connection string as appropriate. Please refer
to the documentation on the <link linkend="dbprovider">DbProvider</link>
abstraction for details particular to your database configuration. You
should also install the Northwind database, which is available for
SqlServer 2005 from this <ulink
url="http://www.microsoft.com/downloads/details.aspx?FamilyID=06616212-0356-46a0-8da2-eebc53a68034&amp;DisplayLang=en">download
location</ulink>. The minimal schema to support other database providers
may be supported in the future.</para>
<section>
<title>AdoTemplate Configuration</title>
<para>The various DAO objects refer to an instance of AdoTemplate
which is responsible for performing data access operations. This is
declared in ExampleTest.xml as shown below</para>
<programlisting> &lt;object id="adoTemplate" type="Spring.Data.Generic.AdoTemplate, Spring.Data"&gt;
&lt;property name="DbProvider" ref="dbProvider"/&gt;
&lt;property name="DataReaderWrapperType" value="Spring.Data.Support.NullMappingDataReader, Spring.Data"/&gt;
&lt;/object&gt;
</programlisting>
<para>The property DbProvider refers to the database configuration you
previously defined. Also the property DataReaderWrapper is set to the
NullMappingDataReader that ships with Spring. This provides convenient
default values for null values returned from the database. To read
more about AdoTemplate, refer to the chapter, <link linkend="???">Data
access using ADO.NET</link>.</para>
</section>
</section>
<section>
<title>CommandCallback</title>
<para>The code that exercises the use of a CommandCallback is shown
below</para>
<programlisting> [Test]
public void CallbackDaoTest()
{
CommandCallbackDao commandCallbackDao = ctx["commandCallbackDao"] as CommandCallbackDao;
int count = commandCallbackDao.FindCountWithPostalCode("1010");
Assert.AreEqual(3, count);
}</programlisting>
<para>The configuration of the CommandCallbackDao is shown below</para>
<programlisting> &lt;object id="commandCallbackDao" type="Spring.DataQuickStart.Dao.GenericTemplate.CommandCallbackDao, Spring.DataQuickStart"&gt;
&lt;property name="AdoTemplate" ref="adoTemplate"/&gt;
&lt;/object&gt;</programlisting>
<para>This the minimal configuration required for a DAO object,
typically DAO objects in your application will include other
configuraiton information, for example properties to specify the maximum
size of the result set returned etc. The implementation of the
FindCountWithPostalCode is shown below</para>
<programlisting> public virtual int FindCountWithPostalCodeWithDelegate(string postalCode)
{
// Using anonymous delegates allows you to easily reference the
// surrounding parameters for use with the DbCommand processing.
return AdoTemplate.Execute&lt;int&gt;(delegate(DbCommand command)
{
// Do whatever you like with the DbCommand... downcast to get
// provider specific funtionality if necesary.
command.CommandText = cmdText;
DbParameter p = command.CreateParameter();
p.ParameterName = "@PostalCode";
p.Value = postalCode;
command.Parameters.Add(p);
return (int)command.ExecuteScalar();
});
}</programlisting>
<para>Anonymous delegates are used to specify the implementation of the
callback function that passes in a DbCommand object. You can then use
the DbCommand object as you see fit to access the database. If you are
using Spring's delcarative transaction management features then this
DbCommand would have its transaction and connection properties based on
the context of the surrounding transaction. All resource management for
the DbCommand are handled for you by the framework, as well as error
reporting on error etc. If you execute the test, it will pass, assuming
you haven't modified any data in the Northwind database from its raw
installation.</para>
</section>
</section>
</chapter>

View File

@@ -0,0 +1,464 @@
<?xml version="1.0" encoding="UTF-8"?>
<chapter id="dbprovider">
<title>DbProvider</title>
<section id="dbprovider-introduction">
<title>Introduction</title>
<para>Spring provides a generic factory for creating ADO.NET API artifacts
such as <code><classname>IDbConnection</classname></code> and
<code><classname>IDbCommand</classname></code>. The factory API is very
similar to the one introduced in .NET 2.0 but adds extra metadata needed
by Spring to support features provided by its DAO/ADO.NET framework such
as error code translation to a DAO exception hierarchy. The factory itself
is configured by using a standard Spring XML based configuration file
though it is unlikely you will need to modify those settings yourself, you
only need be concerned with using the factory. Out of the box several
popular databases are supported and an extension mechanism is available
for defining new database providers or modifying existing ones. A custom
database namespace for configuration aids in making terse XML based
declarations of Spring's database objects you wish to use.</para>
<para>The downside of Spring's factory as compared to the one in .NET 2.0
is that the types returned are lower level interfaces and not the abstract
base classes in System.Data.Common. However, there are still 'holes' in
the current .NET 2.0 provider classes that are 'plugged' with Spring's
provider implementation. One of the most prominent is the that the top
level DbException exposes the HRESULT of the remote procedure call, which
is not what you are commonly looking for when things go wrong. As such
Spring's provider factory exposes the vendor sql error code and also maps
that error code onto a consistent data access exception hierarchy. This
makes writing portable exception handlers much easier. In addition, the
DbParameter class doesn't provide the most common convenient methods you
would expect as when using say the SqlServer provider. If you need to
access the BCL provider abstraction, you still can through Spring's
provider class. Furthermore, a small wrapper around the standard BCL
provider abstraction allows for integration with Spring's transaction
management facilities, allowing you to create a DbCommand with its
connection and transaction properties already set based on the transaction
calling context.</para>
</section>
<section id="dbprovider-dbprovider">
<title>IDbProvider and DbProviderFactory</title>
<para>The <code><interfacename>IDbProvider</interfacename></code> API is
shown below and should look familiar to anyone using .NET 2.0 data
providers. Note that Spring's DbProvider abstraction can be used on .NET
1.1 in addition to .NET 2.0</para>
<programlisting> public interface IDbProvider
{
IDbCommand CreateCommand();
object CreateCommandBuilder();
IDbConnection CreateConnection();
IDbDataAdapter CreateDataAdapter();
IDbDataParameter CreateParameter();
string CreateParameterName(string name);
string CreateParameterNameForCollection(string name);
IDbMetadata DbMetadata
{
get;
}
string ConnectionString
{
set;
get;
}
string ExtractError(Exception e);
bool IsDataAccessException(Exception e);
}</programlisting>
<para>ExtractError is used to return an error string for translation into
a DAO exception. On .NET 1.1 the method IsDataAccessException is used to
determine if the thrown exception is related to data access since in .NET
1.1 there isn't a common base class for database exceptions.
CreateParameterName is used to create the string for parameters used in a
CommandText object while CreateParameterNameForCollection is used to
create the string for a IDataParameter.ParameterName, typically contained
inside a IDataParameterCollection.</para>
<para>The class <classname>DbProviderFactory</classname> creates
IDbProvider instances given a provider name. The connection string
property will be used to set the IDbConnection returned by the factory if
present. The provider names, and corresponding database, currently
configured are listed below.</para>
<itemizedlist>
<listitem>
<para><code>SqlServer-1.1</code> - Microsoft SQL Server, provider
V1.0.5000.0 in framework .NET V1.1</para>
</listitem>
<listitem>
<para><code>SqlServer-2.0</code> (aliased to
<code>System.Data.SqlClient</code>) - Microsoft SQL Server, provider
V2.0.0.0 in framework .NET V2.0</para>
</listitem>
<listitem>
<para><literal>SqlServerCe-3.1</literal> (aliased to
<literal>System.Data.SqlServerCe</literal>) - Microsoft SQL Server
Compact Edition, provider V9.0.242.0</para>
</listitem>
<listitem>
<para><code>OleDb-1.1</code> - OleDb, provider V1.0.5000.0 in
framework .NET V1.1</para>
</listitem>
<listitem>
<para><code>OleDb-2.0</code> (aliased to
<code>System.Data.OleDb</code>) - OleDb, provider V2.0.0.0 in
framework .NET V2.0</para>
</listitem>
<listitem>
<para><code>OracleClient-2.0</code> (aliased to
<code>System.Data.OracleClient</code>) - Oracle, Microsoft provider
V2.0.0.0</para>
</listitem>
<listitem>
<para><code>OracleODP-2.0</code> (aliased to
<code>System.DataAccess.Client</code>) - Oracle, Oracle provider
V2.102.2.20</para>
</listitem>
<listitem>
<para><code>MySql</code> - MySQL, MySQL provider 1.0.10.1</para>
</listitem>
<listitem>
<para><literal>MySql-1.0.9</literal> - MySQL, MySQL provider
1.0.9</para>
</listitem>
<listitem>
<para><literal>MySql-5.0</literal> - MySQL, MySQL provider
5.0.7.0</para>
</listitem>
<listitem>
<para><literal>MySql-5.0.8.1</literal> - MySQL, MySQL provider
5.0.8.1</para>
</listitem>
<listitem>
<para><literal>MySql-5.1 </literal>- MySQL, MySQL provider
5.1.2.2</para>
</listitem>
<listitem>
<para><literal>MySql-5.1.4</literal> - (aliased to
<literal>MySql.Data.MySqlClient</literal>) MySQL, MySQL provider
5.1.2.2</para>
</listitem>
<listitem>
<para><literal>Npgsql-1.0</literal> - Postgresql provider 1.0.0.0 (and
1.0.0.1 - were build with same version info)</para>
</listitem>
<listitem>
<para><literal>Npgsql-2.0-beta1</literal> - Postgresql provider
1.98.1.0 beta 1</para>
</listitem>
<listitem>
<para><literal>DB2-9.0.0-1.1</literal> - IBM DB2 Data Provider 9.0.0
for .NET Framework 1.1</para>
</listitem>
<listitem>
<para><literal>DB2-9.0.0-2.0 </literal>- (aliased to
<literal>IBM.Data.DB2</literal>) - IBM DB2 Data Provider 9.0.0 for
.NET Framework 2.0</para>
</listitem>
<listitem>
<para><literal>DB2-9.1.0-1.1</literal> - IBM DB2 Data Provider 9.1.0
for .NET Framework 1.1</para>
</listitem>
<listitem>
<para><literal>DB2-9.1.0.2</literal> - (aliased to
<literal>IBM.Data.DB2.9.1.0</literal>) - IBM DB2 Data Provider 9.1.0
for .NET Framework 2.0</para>
</listitem>
<listitem>
<para><literal>SQLite-1.0.43 </literal>SQLite provider 1.0.43 for .NET
Framework 2.0</para>
</listitem>
<listitem>
<para><literal>SQLite-1.0.47 </literal>- (aliased to
System.Data.SQLite) - SQLite provider 1.0.43 for .NET Framework
2.0</para>
</listitem>
<listitem>
<para><literal>SybaseAse-12</literal> - Sybase ASE provider for ASE
12.x</para>
</listitem>
<listitem>
<para><literal>SybaseAse-15</literal> - Sybase ASE provider for ASE
15.x</para>
</listitem>
<listitem>
<para><literal>Odbc-1.1</literal> - ODBC provider V1.0.5000.0 in
framework .NET V1.1</para>
</listitem>
<listitem>
<para><literal>Odbc-2.0</literal> - ODBC provider V2.0.0.0 in
framework .NET V2</para>
</listitem>
</itemizedlist>
<para>An example using DbProviderFactory is shown below</para>
<programlisting>IDbProvider dbProvider = DbProviderFactory.GetDbProvider("System.Data.SqlClient");</programlisting>
<para>The default definitions of the providers are contained in the
assembly resource
<code>assembly://Spring.Data/Spring.Data.Common/dbproviders.xml</code>.
Future additions to round out the database coverage are forthcoming. The
current crude mechanism to add additional providers, or to apply any
standard Spring <interfacename>IApplicationContext</interfacename>
functionality, such as applying AOP advice, is to set the public static
property DBPROVIDER_ADDITIONAL_RESOURCE_NAME in
<classname>DbProviderFactory</classname> to a Spring resource location.
The default value is <code>file://dbProviders.xml</code>. (That isn't a
typo, there is a difference in case with the name of the embedded
resource). This crude mechanism will eventually be replaced with one based
on a custom configuration section in App.config/Web.config.</para>
<para>It may happen that the version number of an assembly you have
downloaded is different than the one listed above. If it is a point
release, i.e. the API hasn't changed in anyway that is material to your
application, you should add an assembly redirect of the form shown
below.</para>
<programlisting>&lt;dependentAssembly&gt;
&lt;assemblyIdentity name="MySql.Data"
publicKeyToken="c5687fc88969c44d"
culture="neutral"/&gt;
&lt;bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535"
newVersion="1.0.10.1"/&gt;
&lt;/dependentAssembly&gt;</programlisting>
<para>This redirects any reference to an older version of the assembly
MySql.Data to the version 1.0.10.1.</para>
</section>
<section>
<title>XML based configuration</title>
<para>Creating a DbProvider in Spring's XML configuration file is shown
below in the typical case of using it to specify the DbProvider property
on an AdoTemplate.</para>
<programlisting>&lt;objects xmlns='http://www.springframework.net'
xmlns:db="http://www.springframework.net/database"&gt;
&lt;db:provider id="DbProvider"
provider="System.Data.SqlClient"
connectionString="Data Source=(local);Database=Spring;User ID=springqa;Password=springqa;Trusted_Connection=False"/&gt;
&lt;object id="adoTemplate" type="Spring.Data.AdoTemplate, Spring.Data"&gt;
&lt;property name="DbProvider" ref="DbProvider"/&gt;
&lt;/object&gt;
&lt;/objects&gt;</programlisting>
<para>A custom namespace should be registered in the main application
configuration file to use this syntax. This configuration, only for the
parsers, is shown below. Additional section handlers are needed to specify
the rest of the Spring configuration locations as described in previous
chapters.</para>
<programlisting>&lt;configuration&gt;
&lt;configSections&gt;
&lt;sectionGroup name="spring"&gt;
&lt;section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core" /&gt;
&lt;/sectionGroup&gt;
&lt;/configSections&gt;
&lt;spring&gt;
&lt;parsers&gt;
&lt;parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" /&gt;
&lt;/parsers&gt;
&lt;/spring&gt;
&lt;/configuration&gt;</programlisting>
</section>
<section>
<title>Connection String management</title>
<para>There are a few options available to help manage your connection
strings.</para>
<para>The first option is to leverage the Spring property replacement
functionality, as described in <xref
linkend="objects-factory-placeholderconfigurer" />. This lets you insert
variable names as placeholders for values in a Spring configuration file.
In the following example specific parts of a connection string have been
parameterized but you can also use a variable to set the entire connection
string.</para>
<para>An example of such a setting is shown below</para>
<programlisting>&lt;configuration&gt;
&lt;configSections&gt;
&lt;sectionGroup name="spring"&gt;
&lt;section name='context' type='Spring.Context.Support.ContextHandler, Spring.Core'/&gt;
&lt;/sectionGroup&gt;
&lt;section name="databaseSettings" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /&gt;
&lt;/configSections&gt;
&lt;spring&gt;
&lt;context&gt;
&lt;resource uri="Aspects.xml" /&gt;
&lt;resource uri="Services.xml" /&gt;
&lt;resource uri="Dao.xml" /&gt;
&lt;/context&gt;
&lt;/spring&gt;
&lt;!-- These properties are referenced in Dao.xml --&gt;
&lt;databaseSettings&gt;
&lt;add key="db.datasource" value="(local)" /&gt;
&lt;add key="db.user" value="springqa" /&gt;
&lt;add key="db.password" value="springqa" /&gt;
&lt;add key="db.database" value="Northwind" /&gt;
&lt;/databaseSettings&gt;
&lt;/configuration&gt;</programlisting>
<para>Where <literal>Dao.xml</literal> has a connection string as shown
below</para>
<programlisting>&lt;objects xmlns='http://www.springframework.net'
xmlns:db="http://www.springframework.net/database"&gt;
&lt;db:provider id="DbProvider"
provider="System.Data.SqlClient"
connectionString="${db.datasource};Database=${db.database};User ID=${db.user};Password=${db.password};Trusted_Connection=False"/&gt;
&lt;object id="adoTemplate" type="Spring.Data.AdoTemplate, Spring.Data"&gt;
&lt;property name="DbProvider" ref="DbProvider"/&gt;
&lt;/object&gt;
&lt;!-- configuration of what values to substitute for ${ } variables listed above --&gt;
&lt;object name="appConfigPropertyHolder"
type="Spring.Objects.Factory.Config.PropertyPlaceholderConfigurer, Spring.Core"&gt;
&lt;property name="configSections" value="DatabaseConfiguration"/&gt;
&lt;/object&gt;
&lt;/objects&gt;</programlisting>
<para>Please refer to the Section <xref
linkend="objects-factory-placeholderconfigurer" /> for more
information.</para>
</section>
<section id="dbprovider-additional">
<title>Additional IDbProvider implementations</title>
<para>Spring provides some convenient implementations of the IDbProvider
interface that add addtional behavior on top of the standard
implementation.</para>
<section id="dbprovider-usercredentials">
<title>UserCredentialsDbProvider</title>
<para>This <classname>UserCredentialsDbProvider</classname> will allow
you to change the username and password of a database connection at
runtime. The API contains the properties <literal>Username</literal> and
<literal>Password</literal> which are used as the default strings
representing the user and password in the connection string. You can
then change the value of these properties in the connection string by
calling the method <literal>SetCredentialsForCurrentThread</literal> and
fall back to the default values by calling the method
<literal>RemoveCredentialsFromCurrentThread</literal>. You call the
<literal>SetCredentialsForCurrentThread</literal> method at runtime,
before any data access occurs, to determine which database user should
be used for the current user-case. Which user to select is up to you.
You may retrieve the user information from an HTTP session for example.
Example configuration and usage is shown below</para>
<programlisting>&lt;object id="DbProvider" type="Spring.Data.Common.UserCredentialsDbProvider, Spring.Data"&gt;
&lt;property name="TargetDbProvider" ref="targetDbProvider"/&gt;
&lt;property name="Username" value="User ID=defaultName"/&gt;
&lt;property name="Password" value="Password=defaultPass"/&gt;
&lt;/object&gt;
&lt;db:provider id="targetDbProvider" provider="SqlServer-2.0"
connectionString="Data Source=MARKT60\SQL2005;Database=Spring;Trusted_Connection=False"/&gt;</programlisting>
<para>If you use dependency injection to configure a class with a
property of the type <literal>IDbProvider</literal>, you will need to
downcast to the subtype or you can change your class to have a property
of the type <literal>UserCredentialsDbProvider</literal> instead of
<literal>IDbProvider</literal>.</para>
<programlisting>userCredentialsDbProvider.SetCredentialsForCurrentThread("User ID=springqa", "Password=springqa");</programlisting>
<para><literal>UserCredentialsDbProvider's</literal> has a base class,
<literal>DelegatingDbProvider</literal>, and is intended for you to use
in your own implementations that delegate calls to a target
<literal>IDbProvider</literal> instance. This class in meant to be
subclassed with subclasses overriding only those methods, such as
<literal>CreateConnection()</literal>, that should not simply delegate
to the target <literal>IDbProvider</literal>.</para>
</section>
<section id="dbprovider-multidelegating">
<title>MultiDelegatingDbProvider</title>
<para>There are use-cases in which there will need to be a runtime
selection of the database to connect to among many possible candidates.
This is often the case where the same schema is installed in separate
databases for different clients. The
<classname>MultiDelegatingDbProvider</classname> implements the
<classname>IDbProvider</classname> interface and provides an abstraction
to the multiple databases and can be used in DAO layer such that the DAO
layer is unaware of the switching between databases.
<classname>MultiDelegatingDbProvider</classname> does its job by looking
into thread local storage under the key dbProviderName. This storage
location stores the name of the dbProvider that is to be used for
processing the request. <classname>MultiDelegatingDbProvider</classname>
is configured using the dictionary property
<literal>TargetDbProviders</literal>. The key of this dictionary
contains the name of a dbProvider and its value is a dbProvider object.
(You can also provide this dictionary as a constructor argument.) During
request processing, once you have determined which target dbProvider
should be use, in this example database1ProviderName, you should execute
the following code
<literal>LogicalThreadContext.SetData("dbProviderName",
"database1ProviderName")</literal> and then call the data access
layer.</para>
<para></para>
</section>
</section>
</chapter>

View File

@@ -0,0 +1,990 @@
<?xml version="1.0" encoding="UTF-8"?>
<chapter id="expressions">
<title>Expression Evaluation</title>
<sect1 id="expressions-introduction">
<title>Introduction</title>
<para>The Spring.Expressions namespace provides a powerful expression
language for querying and manipulating an object graph at runtime. The
language supports setting and getting of property values, property
assignment, method invocation, accessing the context of arrays,
collections and indexers, logical and arithmetic operators, named
variables, and retrieval of objects by name from Spring's IoC container.
It also supports list projection and selection, as well as common list
aggregators.</para>
<para>The functionality provided in this namespace serves as the
foundation for a variety of other features in Spring.NET such as enhanced
property evaluation in the XML based configuration of the IoC container, a
Data Validation framework, and a Data Binding framework for ASP.NET. You
will likely find other cool uses for this library in your own work where
run-time evaluation of criteria based on an object's state is required.
For those with a Java background, the Spring.Expressions namespace
provides functionality similar to the Java based Object Graph Navigation
Language, <ulink url="http://www.ognl.org/">OGNL</ulink>.</para>
<para>This chapter covers the features of the expression language using an
Inventor and Inventor's Society class as the target objects for expression
evaluation. The class declarations and the data used to populate them are
listed at the end of the chapter in section <xref
linkend="expressions-classes" />. These classes are blatantly taken from
the NUnit tests for the Expressions namespace which you can refer to for
additional example usage.</para>
</sect1>
<sect1 id="expressions-evaluating">
<title>Evaluating Expressions</title>
<para>The simplest, but not the most efficient way to perform expression
evaluation is by using one of the static convenience methods of the
<classname>ExpressionEvaluator</classname> class:<programlisting>public static object GetValue(object root, string expression);
public static object GetValue(object root, string expression, IDictionary variables)
public static void SetValue(object root, string expression, object newValue)
public static void SetValue(object root, string expression, IDictionary variables, object newValue)</programlisting>
The first argument is the 'root' object that the expression string (2nd argument) will be
evaluated against. The third argument is used to support variables in the expression
and will be discussed later.
Simple usage to get the value of an object property is shown below using the
<classname>Inventor</classname> class.
You can find the class listing in section <xref linkend="expressions-classes"/>.
<programlisting>Inventor tesla = new Inventor("Nikola Tesla", new DateTime(1856, 7, 9), "Serbian");
tesla.PlaceOfBirth.City = "Smiljan";
string evaluatedName = (string) ExpressionEvaluator.GetValue(tesla, "Name");
string evaluatedCity = (string) ExpressionEvaluator.GetValue(tesla, "PlaceOfBirth.City"));</programlisting>
The value of 'evaluatedName' is 'Nikola Tesla' and that of 'evaluatedCity'
is 'Smiljan'. A period is used to navigate the nested properties of the
object. Similarly to set the property of an object, say we want to rewrite
history and change Tesla's city of birth, we would simply add the
following line <programlisting>ExpressionEvaluator.SetValue(tesla, "PlaceOfBirth.City", "Novi Sad");</programlisting></para>
<para>A much better way to evaluate expressions is to parse them once and
then evaluate as many times as you want
using<classname>Expression</classname>class. Unlike
<classname>ExpressionEvaluator</classname>, which parses expression every
time you invoke one of its methods, <classname>Expression</classname>
class will cache the parsed expression for increased performance. The
methods of this class are listed below: <programlisting>public static IExpression Parse(string expression)
public override object Get(object context, IDictionary variables)
public override void Set(object context, IDictionary variables, object newValue)</programlisting>
The retrieval of the Name property in the previous example using the
Expression class is shown below <programlisting>IExpression exp = Expression.Parse("Name");
string evaluatedName = (string) exp.GetValue(tesla, null);</programlisting></para>
<para>The difference in performance between the two approaches, when
evaluating the same expression many times, is several orders of magnitude,
so you should only use convenience methods of the
<classname>ExpressionEvaluator</classname> class when you are doing
one-off expression evaluations. In all other cases you should parse the
expression first and then evaluate it as many times as you need.</para>
<para>There are a few exception classes to be aware of when using the
<classname>ExpressionEvaluator</classname>. These are
<classname>InvalidPropertyException</classname>, when you refer to a
property that doesn't exist,
<classname>NullValueInNestedPathException</classname>, when a null value
is encountered when traversing through the nested property list, and
<classname>ArgumentException</classname> and
<classname>NotSupportedException</classname> when you pass in values that
are in error in some other manner.</para>
<para>The expression language is based on a grammar and uses <ulink
url="http://www.antlr.org/">ANTLR</ulink> to construct the lexer and
parser. Errors relating to bad syntax of the language will be caught at
this level of the language implementation. For those interested in the
digging deeper into the implementation, the grammar file is named
Expression.g and is located in the src directory of the namespace. As a
side note, the release version of the ANTLR DLL included with Spring.NET
was signed with the Spring.NET key, which means that you should always use
the included version of <literal>antlr.runtime.dll</literal> within your
application. Upcoming releases of ANTLR will provide strongly signed
assemblies, which will remove this requirement.</para>
</sect1>
<sect1 id="expressions-language-ref">
<title>Language Reference</title>
<sect2 id="expressions-literals">
<title>Literal expressions</title>
<para>The types of literal expressions supported are strings, dates,
numeric values (int, real, and hex), boolean and null. String are
delimited by single quotes. To put a single quote itself in a string use
the backslash character. The following listing shows simple usage of
literals. Typically they would not be used in isolation like this, but
as part of a more complex expression, for example using a literal on one
side of a logical comparison operator. <programlisting>string helloWorld = (string) ExpressionEvaluator.GetValue(null, "'Hello World'"); // evals to "Hello World"
string tonyPizza = (string) ExpressionEvaluator.GetValue(null, "'Tony\\'s Pizza'"); // evals to "Tony's Pizza"
double avogadrosNumber = (double) ExpressionEvaluator.GetValue(null, "6.0221415E+23");
int maxValue = (int) ExpressionEvaluator.GetValue(null, "0x7FFFFFFF"); // evals to 2147483647
DateTime birthday = (DateTime) ExpressionEvaluator.GetValue(null, "date('1974/08/24')");
DateTime exactBirthday =
(DateTime) ExpressionEvaluator.GetValue(null, " date('19740824T131030', 'yyyyMMddTHHmmss')");
bool trueValue = (bool) ExpressionEvaluator.GetValue(null, "true");
object nullValue = ExpressionEvaluator.GetValue(null, "null");</programlisting>
Note that the extra backslash character in Tony's Pizza is to satisfy C#
escape syntax. Numbers support the use of the negative sign, exponential
notation, and decimal points. By default real numbers are parsed using
<classname>Double.Parse</classname> unless the format character "M" or
"F" is supplied, in which case <classname>Decimal.Parse</classname> and
<classname>Single.Parse</classname> would be used respectfully. As shown
above, if two arguments are given to the date literal then
<classname>DateTime.ParseExact</classname> will be used. Note that all
parse methods of classes that are used internally reference the
<classname>CultureInfo.InvariantCulture</classname>.</para>
</sect2>
<!-- PROPERTIES -->
<sect2 id="expressions-properties">
<title>Properties, Arrays, Lists, Dictionaries, Indexers</title>
<para>As shown in the previous example in <xref
linkend="expressions-evaluating" />, navigating through properties is
easy, just use a period to indicate a nested property value. The
instances of <classname>Inventor</classname> class,
<emphasis>pupin</emphasis> and <emphasis>tesla</emphasis>, were
populated with data listed in section <xref
linkend="expressions-classes" />. To navigate "down" and get Tesla's
year of birth and Pupin's city of birth the following expressions are
used <programlisting>int year = (int) ExpressionEvaluator.GetValue(tesla, "DOB.Year")); // 1856
string city = (string) ExpressionEvaluator.GetValue(pupin, "PlaCeOfBirTh.CiTy"); // "Idvor"</programlisting>
For the sharp-eyed, that isn't a typo in the property name for place of
birth. The expression uses mixed cases to demonstrate that the
evaluation is case insensitive.</para>
<para>The contents of arrays and lists are obtained using square bracket
notation. <programlisting>// Inventions Array
string invention = (string) ExpressionEvaluator.GetValue(tesla, "Inventions[3]"); // "Induction motor"
// Members List
string name = (string) ExpressionEvaluator.GetValue(ieee, "Members[0].Name"); // "Nikola Tesla"
// List and Array navigation
string invention = (string) ExpressionEvaluator.GetValue(ieee, "Members[0].Inventions[6]") // "Wireless communication"</programlisting></para>
<para>The contents of dictionaries are obtained by specifying the
literal key value within the brackets. In this case, because keys for
the <emphasis>Officers</emphasis> dictionary are strings, we can specify
string literal.<programlisting>// Officer's Dictionary
Inventor pupin = (Inventor) ExpressionEvaluator.GetValue(ieee, "Officers['president']";
string city = (string) ExpressionEvaluator.GetValue(ieee, "Officers['president'].PlaceOfBirth.City"); // "Idvor"
ExpressionEvaluator.SetValue(ieee, "Officers['advisors'][0].PlaceOfBirth.Country", "Croatia");</programlisting></para>
<para>You may also specify non literal values in place of the quoted
literal values by using another expression inside the square brackets
such as variable names or static properties/methods on other types.
These features are discussed in other sections.</para>
<para>Indexers are similarly referenced using square brackets. The
following is a small example that shows the use of indexers.
Multidimensional indexers are also supported. <programlisting>public class Bar
{
private int[] numbers = new int[] {1, 2, 3};
public int this[int index]
{
get { return numbers[index];}
set { numbers[index] = value; }
}
}
Bar b = new Bar();
int val = (int) ExpressionEvaluator.GetValue(bar, "[1]") // evaluated to 2
ExpressionEvaluator.SetValue(bar, "[1]", 3); // set value to 3</programlisting></para>
<sect3>
<title>Defining Arrays, Lists and Dictionaries Inline</title>
<para>In addition to accessing arrays, lists and dictionaries by
navigating the graph for the context object, Spring.NET Expression
Language allows you to define them inline, within the expression.
Inline lists are defined by simply enclosing a comma separated list of
items with curly brackets:<programlisting>{1, 2, 3, 4, 5}
{'abc', 'xyz'}</programlisting> If you want to ensure that a strongly typed
array is initialized instead of a weakly typed list, you can use array
initializer instead: <programlisting>new int[] {1, 2, 3, 4, 5}
new string[] {'abc', 'xyz'}</programlisting></para>
<para>Dictionary definition syntax is a bit different: you need to use
a # prefix to tell expression parser to expect key/value pairs within
the brackets and to specify a comma separated list of key/value pairs
within the brackets:<programlisting>#{'key1' : 'Value 1', 'today' : DateTime.Today}
#{1 : 'January', 2 : 'February', 3 : 'March', ...}</programlisting></para>
<para>Arrays, lists and dictionaries created this way can be used
anywhere where arrays, lists and dictionaries obtained from the object
graph can be used, which we will see later in the examples.</para>
<para>Keep in mind that even though examples above use literals as
array/list elements and dictionary keys and values, that's only to
simplify the examples -- you can use any valid expression wherever
literals are used.</para>
</sect3>
</sect2>
<sect2 id="expressions-methods">
<title>Methods</title>
<para>Methods are invoked using typical C# programming syntax. You may
also invoke methods on literals.</para>
<programlisting>//string literal
char[] chars = (char[]) ExpressionEvaluator.GetValue(null, "'test'.ToCharArray(1, 2)")) // 't','e'
//date literal
int year = (int) ExpressionEvaluator.GetValue(null, "date('1974/08/24').AddYears(31).Year") // 2005
// object usage, calculate age of tesla navigating from the IEEE society.
ExpressionEvaluator.GetValue(ieee, "Members[0].GetAge(date('2005-01-01')") // 149 (eww..a big anniversary is coming up ;)</programlisting>
</sect2>
<sect2 id="expressions-operators">
<title>Operators</title>
<sect3 id="expressions-relational">
<title>Relational operators</title>
<para>The relational operators; equal, not equal, less than, less than
or equal, greater than, and greater than or equal are supported using
standard operator notation. These operators take into account if the
object implements the <classname>IComparable</classname> interface.
Enumerations are also supported but you will need to register the
enumeration type, as described in Section <xref
linkend="expressions-typeregistration" />, in order to use an
enumeration value in an expression if it is not contained in the
mscorlib.</para>
<programlisting>ExpressionEvaluator.GetValue(null, "2 == 2") // true
ExpressionEvaluator.GetValue(null, "date('1974-08-24') != DateTime.Today") // true
ExpressionEvaluator.GetValue(null, "2 &lt; -5.0") // false
ExpressionEvaluator.GetValue(null, "DateTime.Today &lt;= date('1974-08-24')") // false
ExpressionEvaluator.GetValue(null, "'Test' &gt;= 'test'") // true</programlisting>
<para>Enumerations can be evaluated as shown below <programlisting>FooColor fColor = new FooColor();
ExpressionEvaluator.SetValue(fColor, "Color", KnownColor.Blue);
bool trueValue = (bool) ExpressionEvaluator.GetValue(fColor, "Color == KnownColor.Blue"); //true</programlisting>
Where FooColor is the following class. <programlisting>public class FooColor
{
private KnownColor knownColor;
public KnownColor Color
{
get { return knownColor;}
set { knownColor = value; }
}
}</programlisting></para>
<para>In addition to standard relational operators, Spring.NET
Expression Language supports some additional, very useful operators
that were "borrowed" from SQL, such as <emphasis>in</emphasis>,
<emphasis>like</emphasis> and <emphasis>between</emphasis>, as well as
<emphasis>is</emphasis> and <emphasis>matches</emphasis> operators,
which allow you to test if object is of a specific type or if the
value matches a regular expression.<programlisting>ExpressionEvaluator.GetValue(null, "3 in {1, 2, 3, 4, 5}") // true
ExpressionEvaluator.GetValue(null, "'Abc' like '[A-Z]b*'") // true
ExpressionEvaluator.GetValue(null, "'Abc' like '?'") // false
ExpressionEvaluator.GetValue(null, "1 between {1, 5}") // true
ExpressionEvaluator.GetValue(null, "'efg' between {'abc', 'xyz'}") // true
ExpressionEvaluator.GetValue(null, "'xyz' is int") // false
ExpressionEvaluator.GetValue(null, "{1, 2, 3, 4, 5} is IList") // true
ExpressionEvaluator.GetValue(null, "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'")) // false
ExpressionEvaluator.GetValue(null, @"'5.00' matches '^-?\d+(\.\d{2})?$'") // true</programlisting>Note
that the Visual Basic and not SQL syntax is used for the
<emphasis>like</emphasis> operator pattern string.
</para>
</sect3>
<sect3 id="expressions-logical">
<title>Logical operators</title>
<para>The logical operators that are supported are
<emphasis>and</emphasis>, <emphasis>or</emphasis>, and
<emphasis>not</emphasis>. Their use is demonstrated
below<programlisting>// AND
bool falseValue = (bool) ExpressionEvaluator.GetValue(null, "true and false"); //false
string expression = @"IsMember('Nikola Tesla') and IsMember('Mihajlo Pupin')";
bool trueValue = (bool) ExpressionEvaluator.GetValue(ieee, expression); //true
// OR
bool trueValue = (bool) ExpressionEvaluator.GetValue(null, "true or false"); //true
string expression = @"IsMember('Nikola Tesla') or IsMember('Albert Einstien')";
bool trueValue = (bool) ExpressionEvaluator.GetValue(ieee, expression); // true
// NOT
bool falseValue = (bool) ExpressionEvaluator.GetValue(null, "!true");
// AND and NOT
string expression = @"IsMember('Nikola Tesla') and !IsMember('Mihajlo Pupin')";
bool falseValue = (bool) ExpressionEvaluator.GetValue(ieee, expression);</programlisting></para>
</sect3>
<sect3 id="expressions-math">
<title>Mathematical operators</title>
<para>The addition operator can be used on numbers, strings and dates.
Subtraction can be used on numbers and dates. Multiplication and
division can be used only on numbers. Other mathematical operators
supported are modulus (%) and exponential power (^). Standard operator
precedence is enforced. These operators are demonstrated below
<programlisting>// Addition
int two = (int)ExpressionEvaluator.GetValue(null, "1 + 1"); // 2
String testString = (String)ExpressionEvaluator.GetValue(null, "'test' + ' ' + 'string'"); //'test string'
DateTime dt = (DateTime)ExpressionEvaluator.GetValue(null, "date('1974-08-24') + 5"); // 8/29/1974
// Subtraction
int four = (int) ExpressionEvaluator.GetValue(null, "1 - -3"); //4
Decimal dec = (Decimal) ExpressionEvaluator.GetValue(null, "1000.00m - 1e4"); // 9000.00
TimeSpan ts = (TimeSpan) ExpressionEvaluator.GetValue(null, "date('2004-08-14') - date('1974-08-24')"); //10948.00:00:00
// Multiplication
int six = (int) ExpressionEvaluator.GetValue(null, "-2 * -3"); // 6
int twentyFour = (int) ExpressionEvaluator.GetValue(null, "2.0 * 3e0 * 4"); // 24
// Division
int minusTwo = (int) ExpressionEvaluator.GetValue(null, "6 / -3"); // -2
int one = (int) ExpressionEvaluator.GetValue(null, "8.0 / 4e0 / 2"); // 1
// Modulus
int three = (int) ExpressionEvaluator.GetValue(null, "7 % 4"); // 3
int one = (int) ExpressionEvaluator.GetValue(null, "8.0 % 5e0 % 2"); // 1
// Exponent
int sixteen = (int) ExpressionEvaluator.GetValue(null, "-2 ^ 4"); // 16
// Operator precedence
int minusFortyFive = (int) ExpressionEvaluator.GetValue(null, "1+2-3*8^2/2/2"); // -45
</programlisting></para>
</sect3>
</sect2>
<sect2 id="expressions-assignment">
<title>Assignment</title>
<para>Setting of a property is done by using the assignment operator.
This would typically be done within a call to
<literal>GetValue</literal> since in the simple case
<literal>SetValue</literal> offers the same functionality. Assignment in
this manner is useful when combining multiple operators in an expression
list, discussed in the next section. Some examples of assignment are
shown below <programlisting>Inventor inventor = new Inventor();
String aleks = (String) ExpressionEvaluator.GetValue(inventor, "Name = 'Aleksandar Seovic'");
DateTime dt = (DateTime) ExpressionEvaluator.GetValue(inventor, "DOB = date('1974-08-24')");
//Set the vice president of the society
Inventor tesla = (Inventor) ExpressionEvaluator.GetValue(ieee, "Officers['vp'] = Members[0]");</programlisting></para>
</sect2>
<sect2 id="expressions-explist">
<title>Expression lists</title>
<para>Multiple expressions can be evaluated against the same context
object by separating them with a semicolon and enclosing the entire
expression within parentheses. The value returned is the value of the
last expression in the list. Examples of this are shown below
<programlisting>//Perform property assignments and then return Name property.
String pupin = (String) ExpressionEvaluator.GetValue(ieee.Members,
"( [1].PlaceOfBirth.City = 'Beograd'; [1].PlaceOfBirth.Country = 'Serbia'; [1].Name )"));
// pupin = "Mihajlo Pupin"</programlisting></para>
</sect2>
<sect2 id="expressions-types">
<title>Types</title>
<para>In many cases, you can reference types by simply specifying type
name:<programlisting>ExpressionEvaluator.GetValue(null, "1 is int")
ExpressionEvaluator.GetValue(null, "DateTime.Today")
ExpressionEvaluator.GetValue(null, "new string[] {'abc', 'efg'}")</programlisting></para>
<para>This is possible for all standard types from
<literal>mscorlib</literal>, as well as for any other type that is
registered with the <literal>TypeRegistry</literal> as described in the
next section.</para>
<para>For all other types, you need to use special
<literal>T(typeName)</literal> expression:<programlisting>Type dateType = (Type) ExpressionEvaluator.GetValue(null, "T(System.DateTime)")
Type evalType = (Type) ExpressionEvaluator.GetValue(null, "T(Spring.Expressions.ExpressionEvaluator, Spring.Core)")
bool trueValue = (bool) ExpressionEvaluator.GetValue(tesla, "T(System.DateTime) == DOB.GetType()")</programlisting></para>
<note>
<para>The implementation delegates to Spring's
<classname>ObjectUtils.ResolveType</classname> method for the actual
type resolution, which means that the types used within expressions
are resolved in the exactly the same way as the types specified in
Spring configuration files.</para>
</note>
</sect2>
<sect2 id="expressions-typeregistration">
<title>Type Registration</title>
<para>To refer to a type within an expression that is not in the
mscorlib you need to register it with the
<literal>TypeRegistry</literal>. This will allow you to refer to a
shorthand name of the type within your expressions. This is commonly
used in expression that use the new operator or refer to a static
properties of an object. Example usage is shown below.</para>
<programlisting>TypeRegistry.RegisterType("Society", typeof(Society));
Inventor pupin = (Inventor) ExpressionEvaluator.GetValue(ieee, "Officers[Society.President]");</programlisting>
<para>Alternatively, you can register types using
<literal>typeAliases</literal> configuration section.</para>
</sect2>
<sect2 id="expressions-ctor">
<title>Constructors</title>
<para>Constructors can be invoked using the new operator. For classes
outside mscorlib you will need to register your types so they can be
resolved. Examples of using constructors are shown below:
<programlisting>// simple ctor
DateTime dt = (DateTime) ExpressionEvaluator.GetValue(null, "new DateTime(1974, 8, 24)");
// Register Inventor type then create new inventor instance within Add method inside an expression list.
// Then return the new count of the Members collection.
TypeRegistry.RegisterType(typeof(Inventor));
int three = (int) ExpressionEvaluator.GetValue(ieee.Members, "{ Add(new Inventor('Aleksandar Seovic', date('1974-08-24'), 'Serbian')); Count}"));
</programlisting></para>
<para>As a convenience, Spring.NET also allows you to define named
constructor arguments, which are used to set object's properties after
instantiation, similar to the way standard .NET attributes work. For
example, you could create an instance of the <literal>Inventor</literal>
class and set its <literal>Inventions</literal> property in a single
statement:<programlisting>
Inventor aleks = (Inventor) ExpressionEvaluator.GetValue(null, "new Inventor('Aleksandar Seovic', date('1974-08-24'), 'Serbian', Inventions = {'SPELL'})");
</programlisting>The only rule you have to follow is that named arguments
should be specified <emphasis>after</emphasis> standard constructor
arguments, just like in the .NET attributes.</para>
<para>While we are on the subject, Spring.NET Expression Language also
provides a convenient syntax for .NET attribute instance creation.
Instead of using standard constructor syntax, you can use a somewhat
shorter and more familiar syntax to create an instance of a .NET
attribute class:<programlisting>
WebMethodAttribute webMethod = (WebMethodAttribute) ExpressionEvaluator.GetValue(null, "@[WebMethod(true, CacheDuration = 60, Description = 'My Web Method')]");
</programlisting>As you can see, with the exception of the
<literal>@</literal> prefix, syntax is exactly the same as in C#.</para>
<para>Slightly different syntax is not the only thing that
differentiates an attribute expression from a standard constructor
invocation expression. In addition to that, attribute expression uses
slightly different type resolution mechanism and will attempt to load
both the specified type name and the specified type name with an
<literal>Attribute</literal> suffix, just like the C# compiler.</para>
</sect2>
<sect2 id="expressions-variables">
<title>Variables</title>
<para>Variables can referenced in the expression using the syntax
<literal>#</literal><emphasis>variableName</emphasis>. The variables are
passed in and out of the expression using the dictionary parameter in
<classname>ExpressionEvaluator</classname>'s <literal>GetValue</literal>
or <literal>SetValue</literal> methods. <programlisting>public static object GetValue(object root, string expression, IDictionary variables)
public static void SetValue(object root, string expression, IDictionary variables, object newValue)</programlisting>
The variable name is the key value of the dictionary. Example usage is
shown below; <programlisting>IDictionary vars = new Hashtable();
vars["newName"] = "Mike Tesla";
ExpressionEvaluator.GetValue(tesla, "Name = #newName", vars));</programlisting>
You can also use the dictionary as a place to store values of the object
as they are evaluated inside the expression. For example to change
Tesla's first name back again and keep the old value; <programlisting>ExpressionEvaluator.GetValue(tesla, "{ #oldName = Name; Name = 'Nikola Tesla' }", vars);
String oldName = (String)vars["oldName"]; // Mike Tesla</programlisting>
Variable names can also be used inside indexers or maps instead of
literal values. For example; <programlisting>vars["prez"] = "president";
Inventor pupin = (Inventor) ExpressionEvaluator.GetValue(ieee, "Officers[#prez]", vars);</programlisting></para>
<sect3 id="expressions-this">
<title>The '#this' and '#root' variables</title>
<para>There are two special variables that are always defined and can
be references within the expression: <literal>#this</literal> and
<literal>#root</literal>.</para>
<para>The <literal>#this</literal> variable can be used to explicitly
refer to the context for the node that is currently being
evaluated:<programlisting>// sets the name of the president and returns its instance
ExpressionEvaluator.GetValue(ieee, "Officers['president'].( #this.Name = 'Nikola Tesla'; #this )")</programlisting></para>
<para>Similarly, the <literal>#root</literal> variable allows you to
refer to the root context for the expression:<programlisting>// removes president from the Officers dictionary and returns removed instance
ExpressionEvaluator.GetValue(ieee, "Officers['president'].( #root.Officers.Remove('president'); #this )")</programlisting></para>
</sect3>
</sect2>
<sect2 id="expressions-ternary">
<title>Ternary Operator (If-Then-Else)</title>
<para>You can use the ternary operator for performing if-then-else
conditional logic inside the expression. A minimal example is;
<programlisting>String aTrueString = (String) ExpressionEvaluator.GetValue(null, "false ? 'trueExp' : 'falseExp'") // trueExp
</programlisting> In this case, the boolean false results in returning the
string value 'trueExp'. A less artificial example is shown below
<programlisting>ExpressionEvaluator.SetValue(ieee, "Name", "IEEE");
IDictionary vars = new Hashtable();
vars["queryName"] = "Nikola Tesla";
string expression = @"IsMember(#queryName)
? #queryName + ' is a member of the ' + Name + ' Society'
: #queryName + ' is not a member of the ' + Name + ' Society'";
String queryResultString = (String) ExpressionEvaluator.GetValue(ieee, expression, vars));
// queryResultString = "Nikola Tesla is a member of the IEEE Society"</programlisting></para>
</sect2>
<sect2>
<title>List Projection and Selection</title>
<para>List projection and selection are very powerful expression
language features that allow you to transform the source list into
another list by either <emphasis>projecting</emphasis> across its
"columns", or <emphasis>selecting</emphasis> from its "rows". In other
words, projection can be thought of as a column selector in a SQL SELECT
statement, while selection would be comparable to the WHERE
clause.</para>
<para>For example, let's say that we need a list of the cities where our
inventors were born. This could be easily obtained by projecting on the
<literal>PlaceOfBirth.City</literal> property: <programlisting>IList placesOfBirth = (IList) ExpressionEvaluator.GetValue(ieee, "Members.!{PlaceOfBirth.City}") // { 'Smiljan', 'Idvor' }
</programlisting>Or we can get the list of officers' names:<programlisting>IList officersNames = (IList) ExpressionEvaluator.GetValue(ieee, "Officers.Values.!{Name}") // { 'Nikola Tesla', 'Mihajlo Pupin' }
</programlisting></para>
<para>As you can see from the examples, projection uses
<literal>!{</literal><emphasis>projectionExpression</emphasis><literal>}</literal>
syntax and will return a new list of the same length as the original
list but typically with the elements of a different type.</para>
<para>On the other hand, selection, which uses
<literal>?{</literal><emphasis>projectionExpression</emphasis><literal>}</literal>
syntax, will filter the list and return a new list containing a subset
of the original element list. For example, selection would allow us to
easily get a list of Serbian inventors:<programlisting>IList serbianInventors = (IList) ExpressionEvaluator.GetValue(ieee, "Members.?{Nationality == 'Serbian'}") // { tesla, pupin }
</programlisting>Or to get a list of inventors that invented
sonar:<programlisting>IList sonarInventors = (IList) ExpressionEvaluator.GetValue(ieee, "Members.?{'Sonar' in Inventions}") // { pupin }
</programlisting>Or we can combine selection and projection to get a list of
sonar inventors' names:<programlisting>IList sonarInventorsNames = (IList) ExpressionEvaluator.GetValue(ieee, "Members.?{'Sonar' in Inventions}.!{Name}") // { 'Mihajlo Pupin' }
</programlisting></para>
<para>As a convenience, Spring.NET Expression Language also supports a
special syntax for selecting the first or last match. Unlike regular
selection, which will return an empty list if no matches are found,
first or last match selection expression will either return an instance
of the matched element, or <literal>null</literal> if no matching
elements were found. In order to return a first match you should prefix
your selection expression with <literal>^{</literal> instead of
<literal>?{</literal>, and to return last match you should use
<literal>${</literal> prefix:<programlisting>ExpressionEvaluator.GetValue(ieee, "Members.^{Nationality == 'Serbian'}.Name") // 'Nikola Tesla'
ExpressionEvaluator.GetValue(ieee, "Members.${Nationality == 'Serbian'}.Name") // 'Mihajlo Pupin'
</programlisting>Notice that we access the <literal>Name</literal> property
directly on the selection result, because an actual matched instance is
returned by the first and last match expression instead of a filtered
list.</para>
</sect2>
<sect2>
<title>Collection Processors and Aggregators</title>
<para>In addition to list projection and selection, Spring.NET
Expression Language also supports several collection processors, such as
<literal>distinct</literal>, <literal>nonNull</literal> and
<literal>sort</literal>, as well as a number of commonly used
aggregators, such as <literal>max</literal>, <literal>min</literal>,
<literal>count</literal>, <literal>sum</literal> and
<literal>average</literal>.</para>
<para>The difference between processors and aggregators is that
processors return a new or transformed collection, while aggregators
return a single value. Other than that, they are very similar -- both
processors and aggregators are invoked on a collection node using
standard method invocation expression syntax, which makes them very
simple to use and allows easy chaining of multiple processors.</para>
<sect3>
<title>Count Aggregator</title>
<para>The count aggregator is a safe way to obtain a number of items
in a collection. It can be applied to a collection of any type,
including arrays, which helps eliminate the decision on whether to use
<literal>Count</literal> or <literal>Length</literal> property
depending on the context. Unlike its standard .NET counterparts, count
aggregator can also be invoked on the <literal>null</literal> context
without throwing a <classname>NullReferenceException</classname>. It
will simply return zero in this case, which makes it much safer than
standard .NET properties within larger expression.<programlisting>ExpressionEvaluator.GetValue(null, "{1, 5, -3}.count()") // 3
ExpressionEvaluator.GetValue(null, "count()") // 0
</programlisting></para>
</sect3>
<sect3>
<title>Sum Aggregator</title>
<para>The sum aggregator can be used to calculate a total for the list
of numeric values. If numbers within the list are not of the same type
or precision, it will automatically perform necessary conversion and
the result will be the highest precision type. If any of the
collection elements is not a number, this aggregator will throw an
<classname>InvalidArgumentException</classname>.<programlisting>ExpressionEvaluator.GetValue(null, "{1, 5, -3, 10}.sum()") // 13 (int)
ExpressionEvaluator.GetValue(null, "{5, 5.8, 12.2, 1}.sum()") // 24.0 (double)
</programlisting></para>
</sect3>
<sect3>
<title>Average Aggregator</title>
<para>The average aggregator will return the average for the
collection of numbers. It will use the same type coercion rules, as
the sum aggregator in order to be as precise as possible. Just like
the sum aggregator, if any of the collection elements is not a number,
it will throw an
<classname>InvalidArgumentException</classname>.<programlisting>ExpressionEvaluator.GetValue(null, "{1, 5, -4, 10}.average()") // 3
ExpressionEvaluator.GetValue(null, "{1, 5, -2, 10}.average()") // 3.5
</programlisting></para>
</sect3>
<sect3>
<title>Minimum Aggregator</title>
<para>The minimum aggregator will return the smallest item in the
list. In order to determine what "the smallest" actually means, this
aggregator relies on the assumption that the collection items are of
the uniform type and that they implement the
<classname>IComparable</classname> interface. If that is not the case,
this aggregator will throw an
<classname>InvalidArgumentException</classname>.<programlisting>ExpressionEvaluator.GetValue(null, "{1, 5, -3, 10}.min()") // -3
ExpressionEvaluator.GetValue(null, "{'abc', 'efg', 'xyz'}.min()") // 'abc'
</programlisting></para>
</sect3>
<sect3>
<title>Maximum Aggregator</title>
<para>The maximum aggregator will return the largest item in the list.
In order to determine what "the largest" actually means, this
aggregator relies on the assumption that the collection items are of
the uniform type and that they implement
<classname>IComparable</classname> interface. If that is not the case,
this aggregator will throw an
<classname>InvalidArgumentException</classname>.<programlisting>ExpressionEvaluator.GetValue(null, "{1, 5, -3, 10}.max()") // 10
ExpressionEvaluator.GetValue(null, "{'abc', 'efg', 'xyz'}.max()") // 'xyz'
</programlisting></para>
</sect3>
<sect3>
<title>Non-null Processor</title>
<para>A non-null processor is a very simple collection processor that
eliminates all <literal>null</literal> values from the
collection.<programlisting>ExpressionEvaluator.GetValue(null, "{ 'abc', 'xyz', null, 'abc', 'def', null}.nonNull()") // { 'abc', 'xyz', 'abc', 'def' }
ExpressionEvaluator.GetValue(null, "{ 'abc', 'xyz', null, 'abc', 'def', null}.nonNull().distinct().sort()") // { 'abc', 'def', 'xyz' }
</programlisting></para>
</sect3>
<sect3>
<title>Distinct Processor</title>
<para>A distinct processor is very useful when you want to ensure that
you don't have duplicate items in the collection. It can also accept
an optional <literal>Boolean</literal> argument that will determine
whether <literal>null</literal> values should be included in the
results. The default is <literal>false</literal>, which means that
they will not be included. <programlisting>ExpressionEvaluator.GetValue(null, "{ 'abc', 'xyz', 'abc', 'def', null, 'def' }.distinct(true).sort()") // { null, 'abc', 'def', 'xyz' }
ExpressionEvaluator.GetValue(null, "{ 'abc', 'xyz', 'abc', 'def', null, 'def' }.distinct(false).sort()") // { 'abc', 'def', 'xyz' }
</programlisting></para>
</sect3>
<sect3>
<title>Sort Processor</title>
<para>The sort processor can be used to sort uniform collections of
elements that implement <classname>IComparable</classname>.</para>
<programlisting>ExpressionEvaluator.GetValue(null, "{1.2, 5.5, -3.3}.sort()") // { -3.3, 1.2, 5.5 }
ExpressionEvaluator.GetValue(null, "{ 'abc', 'xyz', 'abc', 'def', null, 'def' }.sort()") // { null, 'abc', 'abc', 'def', 'def', 'xyz' }
</programlisting>
</sect3>
</sect2>
<sect2>
<title>Spring Object References</title>
<para>Expressions can refer to objects that are declared in Spring's
application context using the syntax
<literal>@(</literal><emphasis>contextName</emphasis><literal>:</literal><emphasis>objectName</emphasis><literal>)</literal>.
If no contextName is specified the default root context name
(<literal>Spring.RootContext</literal>) is used. Using the application
context defined in the MovieFinder example from <xref
linkend="quickstarts" />, the following expression returns the number of
movies directed by Roberto Benigni. <programlisting>public static void Main()
{
. . .
// Retrieve context defined in the spring/context section of
// the standard .NET configuration file.
IApplicationContext ctx = ContextRegistry.GetContext();
int numMovies = (int) ExpressionEvaluator.GetValue(null,
"@(MyMovieLister).MoviesDirectedBy('Roberto Benigni').Length");
. . .
}</programlisting> The variable numMovies is evaluated to 2 in this
example.</para>
</sect2>
<sect2>
<title>Lambda Expressions</title>
<para>A somewhat advanced, but a very powerful feature of Spring.NET
Expression Language are lambda expressions. Lambda expressions allow you
to define inline functions, which can then be used within your
expressions just like any other function or method.</para>
<para>The syntax for defining lambda expressions is:</para>
<para><literal>#</literal><emphasis>functionName</emphasis><literal> =
{|</literal><emphasis>argList</emphasis><literal>|
</literal><emphasis>functionBody</emphasis><literal> }</literal></para>
<para>For example, you could define a <literal>max</literal> function
and call it like this:<programlisting>ExpressionEvaluator.GetValue(null, "(#max = {|x,y| $x &gt; $y ? $x : $y }; #max(5,25))", new Hashtable()) // 25</programlisting></para>
<para>As you can see, any arguments defined for the expression can be
referenced within the function body using a <emphasis>local
variable</emphasis> syntax,
<literal>$</literal><emphasis>varName</emphasis>. Invocation of the
function defined using lambda expression is as simple as specifying the
comma-separated list of function arguments in parentheses, after the
function name.</para>
<para>Lambda expressions can be recursive, which means that you can
invoke the function within its own body:<programlisting>ExpressionEvaluator.GetValue(null, "(#fact = {|n| $n &lt;= 1 ? 1 : $n * #fact($n-1) }; #fact(5))", new Hashtable()) // 120</programlisting></para>
<para>Notice that in both examples above we had to specify a
<literal>variables</literal> parameter for the
<literal>GetValue</literal> method. This is because lambda expressions
are actually nothing more than parameterized variables and we need
variables dictionary in order to store them. If you don't specify a
valid <literal>IDictionary</literal> instance for the
<literal>variables</literal> parameter, you will get a runtime
exception.</para>
<para>Also, in both examples above we used an expression list in order
to define and invoke a function in a single expression. However, more
likely than not, you will want to define your functions once and then
use them within as many expressions as you need. Spring.NET provides an
easy way to pre-register your lambda expressions by exposing a static
<literal>Expression.RegisterFunction</literal> method, which takes
function name, lambda expression and variables dictionary to register
function in as parameters:<programlisting>IDictionary vars = new Hashtable();
Expression.RegisterFunction("sqrt", "{|n| Math.Sqrt($n)}", vars);
Expression.RegisterFunction("fact", "{|n| $n &lt;= 1 ? 1 : $n * #fact($n-1)}", vars);</programlisting>Once
the function registration is done, you can simply evaluate an expression
that uses these functions, making sure that the <literal>vars</literal>
dictionary is passed as a parameter to expression evaluation
engine:<programlisting>ExpressionEvaluator.GetValue(null, "#fact(5)", vars) // 120
ExpressionEvaluator.GetValue(null, "#sqrt(9)", vars) // 3</programlisting></para>
<para>Finally, because lambda expressions are treated as variables, they
can be assigned to other variables or passed as parameters to other
lambda expressions. In the following example we are defining a delegate
function that accepts function <literal>f</literal> as the first
argument and parameter <literal>n</literal> that will be passed to
function <literal>f</literal> as the second. Then we invoke the
functions registered in the previous example, as well as the lambda
expression defined inline, through our delegate:<programlisting>Expression.RegisterFunction("delegate", "{|f, n| $f($n) }", vars);
ExpressionEvaluator.GetValue(null, "#delegate(#sqrt, 4)", vars) // 2
ExpressionEvaluator.GetValue(null, "#delegate(#fact, 5)", vars) // 120
ExpressionEvaluator.GetValue(null, "#delegate({|n| $n ^ 2 }, 5)", vars) // 25</programlisting>While
this particular example is not particularly useful, it does demonstrate
that lambda expressions are indeed treated as nothing more than
parameterized variables, which is important to remember.</para>
</sect2>
<sect2>
<title>Null Context</title>
<para>If you do not specify a root object, i.e. pass in null, then the
expressions evaluated either have to be literal values, i.e.
ExpressionEvaluator.GetValue(null, "2 + 3.14"), refer to classes that
have static methods or properties, i.e.
ExpressionEvaluator.GetValue(null, "DateTime.Today"), create new
instances of objects, i.e. ExpressionEvaluator.GetValue(null, "new
DateTime(2004, 8, 14)") or refer to other objects such as those in the
variable dictionary or in the IoC container. The latter two usages will
be discussed later.</para>
</sect2>
</sect1>
<!-- SAMPLE CLASSES AND DATA -->
<sect1 id="expressions-classes">
<title>Classes used in the examples</title>
<para>The following simple classes are used to demonstrate the
functionality of the expression language.</para>
<programlisting>public class Inventor
{
public string Name;
public string Nationality;
public string[] Inventions;
private DateTime dob;
private Place pob;
public Inventor() : this(null, DateTime.MinValue, null)
{}
public Inventor(string name, DateTime dateOfBirth, string nationality)
{
this.Name = name;
this.dob = dateOfBirth;
this.Nationality = nationality;
this.pob = new Place();
}
public DateTime DOB
{
get { return dob; }
set { dob = value; }
}
public Place PlaceOfBirth
{
get { return pob; }
}
public int GetAge(DateTime on)
{
// not very accurate, but it will do the job ;-)
return on.Year - dob.Year;
}
}
public class Place
{
public string City;
public string Country;
}
public class Society
{
public string Name;
public static string Advisors = "advisors";
public static string President = "president";
private IList members = new ArrayList();
private IDictionary officers = new Hashtable();
public IList Members
{
get { return members; }
}
public IDictionary Officers
{
get { return officers; }
}
public bool IsMember(string name)
{
bool found = false;
foreach (Inventor inventor in members)
{
if (inventor.Name == name)
{
found = true;
break;
}
}
return found;
}
}</programlisting>
<para>The code listings in this chapter use instances of the data
populated with the following information.</para>
<programlisting>Inventor tesla = new Inventor("Nikola Tesla", new DateTime(1856, 7, 9), "Serbian");
tesla.Inventions = new string[]
{
"Telephone repeater", "Rotating magnetic field principle",
"Polyphase alternating-current system", "Induction motor",
"Alternating-current power transmission", "Tesla coil transformer",
"Wireless communication", "Radio", "Fluorescent lights"
};
tesla.PlaceOfBirth.City = "Smiljan";
Inventor pupin = new Inventor("Mihajlo Pupin", new DateTime(1854, 10, 9), "Serbian");
pupin.Inventions = new string[] {"Long distance telephony &amp; telegraphy", "Secondary X-Ray radiation", "Sonar"};
pupin.PlaceOfBirth.City = "Idvor";
pupin.PlaceOfBirth.Country = "Serbia";
Society ieee = new Society();
ieee.Members.Add(tesla);
ieee.Members.Add(pupin);
ieee.Officers["president"] = pupin;
ieee.Officers["advisors"] = new Inventor[] {tesla, pupin};</programlisting>
</sect1>
</chapter>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Some files were not shown because too many files have changed in this diff Show More