Spring Modules - Modules, add-ons and integration tools for Spring

Table of Contents

Preface

1. Introduction 2. Hivemind Integration
2.1. Introduction 2.2. Configure an Hivemind Registry 2.3. Exposing HiveMind Services as Spring Beans
3. JSR94
3.1. Introduction 3.2. JSR94 support
3.2.1. Provider 3.2.2. Administration 3.2.3. Execution 3.2.4. Definition of a ruleset 3.2.5. Configure the JSR94 template 3.2.6. Using the JSR94 template
3.3. Configuration with different engines
3.3.1. JRules 3.3.2. Jess 3.3.3. Drools
4. Commons Validator
4.1. Introduction 4.2. Configure an Validator Factory 4.3. Use a dedicated validation-rules.xml 4.4. Configure a Commons Validator 4.5. Server side validation 4.6. Client side validation
5. Caching
5.1. Introduction 5.2. Uses 5.3. Configuration 5.4. Cache Provider
5.4.1. EHCache 5.4.2. JBoss Cache 5.4.3. Java Caching System (JCS) 5.4.4. OSCache
5.5. Declarative Caching Services
5.5.1. Caching Advice 5.5.2. Caching Models 5.5.3. Caching Listeners 5.5.4. Key Generator 5.5.5. Flushing Advice 5.5.6. Flushing Models
5.6. Strategies for Declarative Caching Services
5.6.1. CacheProxyFactoryBean 5.6.2. Source-level Metadata-driven Autoproxy
5.6.2.1. Jakarta Commons-Attributes 5.6.2.2. JDK 1.5+ Annotations
5.6.3. BeanNameAutoProxyCreator
5.7. Programmatic Use
6. Java Content Repository (JSR-170) Support
6.1. Introduction 6.2. JSR standard support
6.2.1. SessionFactory 6.2.2. Inversion of Control: JcrTemplate and JcrCallback
6.2.2.1. Implementing Spring-based DAOs without callbacks
6.2.3. RepositoryFactoryBean
6.3. Extensions support
6.3.1. JackRabbit
6.3.1.1. Transaction Management
6.4. Mapping support
7. Lucene
7.1. Introduction 7.2. Indexing
7.2.1. IndexFactory 7.2.2. Configuration
7.2.2.1. Configuring directories 7.2.2.2. Configuring a SimpleIndexFactory
7.2.3. Document type handling 7.2.4. Template approach 7.2.5. Mass indexing approach
7.2.5.1. Indexing directories 7.2.5.2. Indexing databases
7.2.6. IndexFactory management
7.3. Search
7.3.1. SearchFactory 7.3.2. Configuration
7.3.2.1. Configuring a SimpleSearcherFactory 7.3.2.2. Configuring a MultipleSearcherFactory 7.3.2.3. Configuring a ParallelMultipleSearcherFactory 7.3.2.4. Configuring a remote SearcherFactory
7.3.3. Make a search
7.3.3.1. Template approach 7.3.3.2. Object approach
7.3.4. SearchFactory management
8. OSWorkflow

Preface

This document provides a reference guide to Spring Modules' features. Spring Modules contains various add-ons for the core Spring Framework and as such this document assumes that you are already familiar with Spring itself. Since this document is still a work-in-progress, if you have any requests, or comments, please post them on the user mailing list or on the Spring Modules forum. If you want to report a bug please use the Spring Modules issue tracking .

Before we go on, a few words of gratitude: Chris Bauer (of the Hibernate team) prepared and adapted the DocBook-XSL software in order to be able to create Hibernate's reference guide, also allowing us to create this one.

Chapter 1. Introduction

TODO: describe all the modules of the project.

Chapter 2. Hivemind Integration

2.1. Introduction

Hivemind is lightweight container providing IoC capabilities similar to Spring. More information about HiveMind can be found at: http://jakarta.apache.org/hivemind.

2.2. Configure an Hivemind Registry

In HiveMind, the Registry is the central location from which your application can gain access to services and configuration data. The RegistryFactoryBean allows for a HiveMind Registry to be configured and started within the Spring ApplicationContext:

There are two ways configure this Registry with the RegistryFactoryBean class:

  • No configuration location is specified. In this case, Hivemind looks for an XML file named hivemodule.xml in the META-INF directory.

  • One or more configuration file locations are specified. In this case, Spring Modules will use these configuration files to configure Registry instance.

The code below shows how to configure a RegistryFactoryBean that loads Registry configuration from a file called configuration.xml:

<bean id="hivemindRegistry" class="org.springmodules.hivemind.RegistryFactoryBean">  <property name="configLocations">    <value>configuration.xml</value>  </property></bean>

The RegistryFactoryBean uses Spring's resource abstraction layer allowing you to specify any valid Spring Resource path for the configLocations property.

2.3. Exposing HiveMind Services as Spring Beans

Using the ServiceFactoryBean it i spossible to expose any service defined in a HiveMind Registry to your application as a Spring bean. This can be desirable if you want to make use of features found in both products but you want your application to code only to one.

The ServiceFactoryBean class requires access to a HiveMind Registry, and as such, you generally need to configure both a RegistryFactoryBean and a ServiceFactoryBean as shown below:

<bean id="registry" class="org.springmodules.hivemind.RegistryFactoryBean">  <property name="configLocations">    <value>configuration.xml</value>  </property></bean><bean id="sampleService" class="org.springmodules.hivemind.ServiceFactoryBean">  <property name="registry">    <ref local="registry"/>  </property>  <property name="serviceInterface">    <value>org.springmodules.samples.hivemind.service.ISampleService</value>  </property>  <property name="serviceName">    <value>interfaces.SampleService</value>  </property></bean>

Whether you define both serviceInterface and serviceName or just serviceInterface depends on how your HiveMind Registry is configured. Consult the HiveMind documentation for more details on how HiveMind services are identified and accessed.

Chapter 3. JSR94

3.1. Introduction

As described in the scope section of the specification document, JSR94 defines a lightweight-programming interface. Its aim is to constitute a standard API for acquiring and using a rule engine.

"The scope of the specification specifically excludes defining a standard rule description language to describe the rules within a rule execution set. The specification targets both the J2SE and J2EE (managed) environments.

The following items are in the scope of the specification:

  • The restrictions and limits imposed by a compliant implementation.

  • The mechanisms to acquire interfaces to a compliant implementation.

  • The mechanisms to acquire interfaces to a compliant implementation.

  • The mechanisms to acquire interfaces to a compliant implementation.

  • The interfaces through which rule execution sets are invoked by runtime clients of a complaint implementation.

  • The interfaces through which rule execution sets are loaded from external resources and registered for use by runtime clients of a compliant implementation.

The following items are outside the scope of the specification:

  • The binary representation of rules and rule execution sets.

  • The syntax and file-formats of rules and rule execution sets.

  • The semantics of interpreting rules and rule execution sets.

  • The mechanism by which rules and rule execution sets are transformed for use by a rule engine.

  • All minimal system requirements required to support a compliant implementation."

Spring Modules provides a support for this specification in order to simply the use of its APIs according to the philosophy of the Spring framework.

3.2. JSR94 support

This section describes the different abstractions to configure in order to administer and use rule engines with the JSR94 support.

3.2.1. Provider

The first step to use JSR94 in a local scenario is to configure the rule engine provider. You must specify its name with the provider property and its implementation class with the providerClass property.

These properties are specific to the used rule engine. For more informations about the configuration of different rule engines, see the following configuration section.

Here is a sample configuration of a rule provider:

<bean id="ruleServiceProvider"       class="org.springmodules.jsr94.factory.DefaultRuleServiceProviderFactoryBean">  <property name="provider">    <value>org.jcp.jsr94.jess</value>  </property>  <property name="providerClass">    <value>org.jcp.jsr94.jess.RuleServiceProviderImpl</value>  </property></bean>

Important note: When you get the JSR94 RuleAdministrator and RuleRuntime from JNDI, you don't need to configure this bean in Spring.

3.2.2. Administration

There are two possibilities to configure the RuleAdministrator abstraction:

  • Local configuration as a bean.

  • Remote access from JNDI.

These two scenarios are supported. Firstly, the local configuration uses the RuleAdministratorFactoryBean which needs to have a reference to the JSR94 provider, configured in the previous section, with its serviceProvider property.

<bean id="ruleAdministrator" class="org.springmodules.jsr94.factory.RuleAdministratorFactoryBean">  <property name="serviceProvider">    <ref local="ruleServiceProvider"/>  </property></bean>

The version 0.1 doesn't support the configuration of a RuleAdministrator from JNDI with a Spring's FactoryBean.

3.2.3. Execution

As for the RuleRuntime abstraction, there are two possibilities to configure the RuleRuntime abstraction (local and from JNDI).

Here is a sample of local configuration as bean:

<bean id="ruleRuntime" class="org.springmodules.jsr94.factory.RuleRuntimeFactoryBean">  <property name="serviceProvider">    <ref local="ruleServiceProvider"/>  </property></bean>

The version 0.1 doesn't support the configuration of a RuleRuntime from JNDI with a Spring's FactoryBean.

3.2.4. Definition of a ruleset

To administer and execute rules, the JSR94 support introduces the RuleSource abstraction. It provides two different features:

  • Automatic configuration of the rule or ruleset for a rule engine.

  • Wrapper of JSR94 APIs for execution.

Important note: A RuleSource is a representation of an unique rule or ruleset.

These two features work respectively upon the JSR94 RuleAdministrator and RuleRuntime abstractions. That's why , to configure the RuleSource, you have two possibilities:

  • Firstly, you can inject these two beans previously configured (see the two previous sections).

  • Secondly, you can inject the JSR94 provider. So the rule source will create automatically these two beans .

You need to specify too some specific properties for the rule:

  • Bind uri of the rule. The value of the bindUriproperty will be use when invoking the corresponding rule.

  • Implementation of the rule. The JSR94 support is based on the Spring resource concept and the source property is managed in this way. So, by default, the ruleset source file is looked for in the classpath.

Here is a sample rule set configuration using the DefaultRuleSource class with a RuleRuntime and a RuleAdministrator:

<bean id="ruleSource" class="org.springmodules.jsr94.rulesource.DefaultRuleSource">  <property name="ruleRuntime">    <ref local="ruleRuntime"/>  </property>  <property name="ruleAdministrator">    <ref local="ruleAdministrator"/>  </property>  <property name="source">    <value>/testagent.drl</value>  </property>  <property name="bindUri">    <value>testagent</value>  </property></bean>

Here is an other sample of rule set configuration using the DefaultRuleSource with a RuleServiceProvider.

<bean id="ruleSource" class="org.springmodules.jsr94.rulesource.DefaultRuleSource">  <property name="ruleServiceProvider">    <ref local="ruleServiceProvider"/>  </property>  <property name="source">    <value>/testagent.drl</value>  </property>  <property name="bindUri">    <value>testagent</value>  </property></bean>

Important note: If you don't specify the bindUri property, the JSR94 support will use the string returned by the getName method of the underlying RuleExecutionSet created for the RuleSource.

On the other hand, JSR94 provides some ways to specify additional configuration properties for specific rule engines.

Firstly some rule engines need to have custom properties to configure rules. These properties can be specify with the ruleSetProperties property of the type Map. This property is passed to the createRuleExecutionSet method (the last argument) of the JSR94 LocalRuleExecutionSetProvider interface. For example, JRules needs to specifiy the rulesetProperties property (see the configuration section).

Then some parameters need to be specified in order to get an implementation of the JSR94 LocalRuleExecutionSetProvider abstraction. These properties can be specify with the providerProperties property as a map.

Finally some parameters need to be specified in order to register a JSR94 RuleExecutionSet implementation.

Here is the the code of the registerRuleExecutionSets method of DefaultRuleSource class to show how the previous maps are used. Note that the DefaultRuleSource class is the default implementation of the RuleSource interface of the JSR94 support.

RuleExecutionSet ruleExecutionSet = ruleAdministrator.    getLocalRuleExecutionSetProvider(providerProperties).createRuleExecutionSet(source.getInputStream(), rulesetProperties);ruleAdministrator.registerRuleExecutionSet(bindUri, ruleExecutionSet, registrationProperties);

3.2.5. Configure the JSR94 template

In order to execute rules, you need to use the dedicated JSR94Template class. This class must be configured with a RuleSource instance.

There are two ways to configure this class.

Firstly, you can define the template directly in Spring as a bean. In this way, you can make your service extend the Jsr94Support abstract class. This class defines get/set methods for the JSR94Template and provides the associated template to the service thanks to the getJSR94Template method.

<bean id="ruleSource" class="org.springmodules.jsr94.rulesource.DefaultRuleSource">  ...</bean><bean id="jsr94Template" class="org.springmodules.jsr94.core.Jsr94Template">  <property name="ruleSource"><ref local="ruleSource"/></property></bean><bean id="myService" class="MyService">  <property name="template"><ref local="jsr94Template"/></property></bean>

Secondly, you can directly inject the configured RuleSource in your service. You can make too your service extend the JSR94Support abstract class. This class defines get/set methods for the RuleSource, creates automatically and provides the associated template to the service thanks to the getJSR94Template method.

<bean id="ruleSource" class="org.springmodules.jsr94.rulesource.DefaultRuleSource">  ...</bean><bean id="myService" class="MyService">  <property name="ruleSource">    <ref local="ruleSource"/>  </property></bean>

Then the MyService class can directly use the template (injected or created with the RuleSource) with the help of the getJSR94Template method.

public class MyServiceImpl extends JSR94Support implements MyService {  public void serviceMethod() {    getJSR94Template.execute(...);  }}

Important note: Because Java doesn't support multiple inheritance, you can't always extend Jsr94Support class because your service classes can already have a super class. In this case, you need to define the get/set methods or instance the template by yourself.

3.2.6. Using the JSR94 template

In order to execute rules, you need to use the JSR94Template class configured in the previous section.

JSR94 defines two session modes to execute rules. A session is a runtime connection between the client and the rule engine.

  • Stateless mode. "A stateless rule session provides a high-performance and simple API that executes a rule execution set with a List of input objects." (quotation of the JSR94 specification)

  • Stateful mode. "A stateful rule session allows a client to have a prolonged interaction with a rule execution set. Input objects can be progressively added to the session and output objects can be queried repeatedly." (quotation of the JSR94 specification)

So this template defines two corresponding executing methods: executeStateless for stateless sessions and executeStateful for stateful ones.

To execute rules in a stateless mode, you need to use the following execute method of the template.

public Object executeStateless(final String uri, final Map properties,                             final StatelessRuleSessionCallback callback) {  //...}

This method needs an implementation of the callback interface, StatelessRuleSessionCallback. This interface defines a method to which an instance of StatelessRuleSession is provided. The developer doesn't need to deal with the release of the resources and the management of technical exceptions.

Moreover, if you need to specify additional parameters to create the session, you can use the second parameter of the method (named properties and which is a map).

public interface StatelessRuleSessionCallback {  Object execute(StatelessRuleSession session)      throws InvalidRuleSessionException, RemoteException;}

Here is a sample of use:

List inputObjects=...;List outputObjects=getTemplate().executeStateless("ruleBindUri",null,  new StatelessRuleSessionCallback() {    public Object execute(StatelessRuleSession session)                  throws InvalidRuleSessionException, RemoteException {      return session.executeRules(inputObjects);    }});

The JSR94 support uses the same features to execute rules in a stateful mode. Here is the dedicated executing method.

public Object executeStateful(final String uri, final Map properties,                          final StatefulRuleSessionCallback callback) {  //...}

This method needs an implementation of the callback interface, StatefulRuleSessionCallback. This interface defines a method to which an instance of StatefulRuleSession is provided. As for stateless sessions, the developer doesn't need to deal with the release of the resources and the management of technical exception.

Moreover, if you need to specify additional parameters to create the session, you can use the second parameter of the method (named properties and which is a map).

public interface StatefulRuleSessionCallback {  Object execute(StatefulRuleSession session)    throws InvalidRuleSessionException, InvalidHandleException, RemoteException;}

Here is a sample of use:

List inputObjects=...;List outputObjects=getTemplate().executeStateful("ruleBindUri",null,  new StatefulRuleSessionCallback() {    public Object execute(StatelessRuleSession session)                  throws InvalidRuleSessionException, RemoteException {      statefulRuleSession.addObjects(inputs);      statefulRuleSession.executeRules();      return statefulRuleSession.getObjects();    }});

3.3. Configuration with different engines

This section will describe the way to configure different rule engines in Spring using the JSR 94 support. This section describes the configuration of the following rule engines:

Although all samples inject RuleRuntime and RuleAdministrator instances, you can inject the JSR94 provider used directly in a local scenario (according to previous sections of the documentation).

3.3.1. JRules

With JSR94, you can only access rules configured in an embedded rule engine. At the time of writing, JRules 5.0 doesn't provide an implementation of JSR94 to execute and administer rules deployed in an BRES (Business Rule Engine Server).

Important note: To use the BRES with Spring, you need to make your own integration code direclty based on the JRules APIs.

Firstly you need to configure the JSR94 provider specific to JRules. The name of the class for JRules is ilog.rules.server.jsr94.IlrRuleServiceProvider. There is no need to define specific parameters for the RuleRuntime and RuleAdministrator beans.

<bean id="ruleServiceProvider"       class="org.springmodules.jsr94.factory.DefaultRuleServiceProviderFactoryBean">  <property name="provider"><value>http://www.ilog.com</value></property>  <property name="providerClass">    <value>ilog.rules.server.jsr94.IlrRuleServiceProvider</value>  </property></bean><bean id="ruleRuntime" class="org.springmodules.jsr94.factory.RuleRuntimeFactoryBean">  <property name="serviceProvider"><ref local="ruleServiceProvider"/></property></bean><bean id="ruleAdministrator"      class="org.springmodules.jsr94.factory.RuleAdministratorFactoryBean">  <property name="serviceProvider"><ref local="ruleServiceProvider"/></property></bean>

Then you need to configure the different rulesets for the embedded rule engine. In order to do this, the DefaultRuleSource can be used. You need to inject the instances of RuleRuntime and RuleAdministrator, specifiy the source of the ruleset (an irl file in the case of JRule and the binding uri for this ruleset.

Note: The language to write JRules' ruleset is IRL (Ilog Rule Language). This language is similar to Java and introduces specific keyworks for rules.

Endly, you need to configure specific properties for JRules:

  • IlrName: This key describes the internal name of the configured ruleset.

  • IlrRulesInILR: This key specifies that the ruleset of the configured file is written in IRL.

<bean id="ruleSource" class="org.springmodules.jsr94.rulesource.DefaultRuleSource">  <property name="ruleRuntime"><ref local="ruleRuntime"/></property>  <property name="ruleAdministrator"><ref local="ruleAdministrator"/></property>  <property name="source"><value>/cars_rules.irl</value></property>  <property name="bindUri"><value>cars</value></property>  <property name="rulesetProperties">    <map>      <entry key="IlrName"><value>cars_rules</value></entry>      <entry key="IlrRulesInILR"><value>true</value></entry>    </map>  </property></bean>

3.3.2. Jess

The reference implementation of the JSR94 specification is a wrapper for the Jess rule engine. We have used the samples provided in the specification to describe the configuration of this rule engine.

Firstly you need to configure the RuleServiceProvider, RuleAdministrator and RuleRuntime abstractions as beans in Spring.

<bean id="ruleServiceProvider"      class="org.springmodules.jsr94.factory.DefaultRuleServiceProviderFactoryBean">  <property name="provider"><value>org.jcp.jsr94.jess</value></property>  <property name="providerClass">    <value>org.jcp.jsr94.jess.RuleServiceProviderImpl</value>  </property></bean><bean id="ruleRuntime"      class="org.springmodules.jsr94.factory.RuleRuntimeFactoryBean">  <property name="serviceProvider"><ref local="ruleServiceProvider"/></property></bean><bean id="ruleAdministrator"      class="org.springmodules.jsr94.factory.RuleAdministratorFactoryBean">  <property name="serviceProvider"><ref local="ruleServiceProvider"/></property></bean>

Then you need to configure rulesets in Spring using the JSR94 support.

<bean id="ruleSource" class="org.springmodules.jsr94.rulesource.DefaultRuleSource">  <property name="ruleRuntime"><ref local="ruleRuntime"/></property>  <property name="ruleAdministrator"><ref local="ruleAdministrator"/></property>  <property name="source"><value>/org/jcp/jsr94/tck/tck_res_1.xml</value></property>  <property name="bindUri"><value>tck_res_1</value></property></bean>

Jess doesn't need specific additional configuration for the rule source.

3.3.3. Drools

An other interesting rule engine is Drools. It provides too an integration with JSR94. We have used the samples provided in the Drools distribution to describe its configuration.

Firstly you need to configure the RuleServiceProvider, RuleAdministrator and RuleRuntime abstractions as beans in Spring.

<bean id="ruleServiceProvider"      class="org.springmodules.jsr94.factory.DefaultRuleServiceProviderFactoryBean">  <property name="provider"><value>http://drools.org/</value></property>  <property name="providerClass">    <value>org.drools.jsr94.rules.RuleServiceProviderImpl</value>  </property></bean><bean id="ruleRuntime"      class="org.springmodules.jsr94.factory.RuleRuntimeFactoryBean">  <property name="serviceProvider"><ref local="ruleServiceProvider"/></property></bean><bean id="ruleAdministrator"      class="org.springmodules.jsr94.factory.RuleAdministratorFactoryBean">  <property name="serviceProvider"><ref local="ruleServiceProvider"/></property></bean>

Then you need to configure rulesets in Spring using the JSR94 support.

<bean id="ruleSource" class="org.springmodules.jsr94.rulesource.DefaultRuleSource">  <property name="ruleRuntime"><ref local="ruleRuntime"/></property>  <property name="ruleAdministrator"><ref local="ruleAdministrator"/></property>  <property name="source"><value>/testagent.drl</value></property>  <property name="bindUri"><value>testagent</value></property></bean>

As Jess, Drools doesn't need specific additional configuration for the rule source.

Chapter 4. Commons Validator

4.1. Introduction

The Commons Validator is a library that allows you to perform validation based on rules specified in XML configuration files.

TODO: Describe the concepts of Commons Validator in more details.

4.2. Configure an Validator Factory

Firstly you need to configure the Validator Factory which is the factory to get Validator instances. To do so, the support provides the class DefaultValidatorFactory in the package org.springmodules.commons.validator.

You need to specify with the property validationConfigLocations the file containing the Commons Validator rules and the file containing the validation rules specific to the application.

The following code shows how to configure this factory.

<bean id="validatorFactory"      class="org.springmodules.commons.validator.DefaultValidatorFactory">  <property name="validationConfigLocations">    <list>      <value>/WEB-INF/validator-rules.xml</value>      <value>/WEB-INF/validation.xml</value>    </list>  </property></bean>

4.3. Use a dedicated validation-rules.xml

The file validation-rules.xml must contain Commons Validator elements based on classes provided by the support of this framework in Spring Modules.

For example, the configuration of the entities "required" and "requiredif" must be now in the validation-rules.xml file.

<validator name="required"          classname="org.springmodules.commons.validator.FieldChecks"          method="validateRequired"          methodParams="java.lang.Object,                        org.apache.commons.validator.ValidatorAction,                        org.apache.commons.validator.Field,                        org.springframework.validation.Errors"          msg="errors.required">  <javascript><![CDATA[    (...)  ]]></javascript></validator><validator name="requiredif"           classname="org.springmodules.commons.validator.FieldChecks"           method="validateRequiredIf"           methodParams="java.lang.Object,                         org.apache.commons.validator.ValidatorAction,                         org.apache.commons.validator.Field,                         org.springframework.validation.Errors,                         org.apache.commons.validator.Validator"          msg="errors.required"></validator>

The validation sample of the distribution provides a complete validation-rules.xml based on the classes of the support.

You must note that the support of validwhen is not provided at the moment in the support. However, some codes are provides in JIRA. For more informations, see the issues MOD-38 and MOD-49.

4.4. Configure a Commons Validator

Then you need to configure the Validator itself basing the previous Validator Factory. It corresponds to an adapter in order to hide Commons Validator behind a Spring Validator.

The following code shows how to configure this validator.

<bean id="beanValidator" class="org.springmodules.commons.validator.DefaultBeanValidator">  <property name="validatorFactory" ref="validatorFactory"/></bean>

4.5. Server side validation

Spring MVC provides the implementation SimpleFormController of the interface Controller in order to process HTML forms. It allows a validation of informations processing by the controller by using the property validator of the controller. In the case of Commons Validator, this property must be set with the bean beanValidator previously configured.

The following code shows how to configure a controller which validates a form on the server side using the support of Commons Validator.

<bean id="myFormController" class="org.springmodules.sample.MyFormController">  (...)  <property name="validator" ref="beanValidator"/>  <property name="commandName" value="myForm"/>  <property name="commandClass" value="org.springmodules.sample.MyForm"/>  (...)</bean>

The beanValidator bean uses the value of the property commandClass of the controller to select the name of the form tag in the validation.xml file. The configuration is not based on the commandName property. For example, with the class name org.springmodules.sample.MyForm, Commons Validator must contain a form tag with myForm as value of the name property. The following code shows the contents of this file.

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE form-validation PUBLIC     "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1//EN"     "http://jakarta.apache.org/commons/dtds/validator_1_1.dtd"><form-validation>  <formset>    <form name="myForm">      <field property="field1" depends="required">        <arg0 key="error.field1" />      </field>      <field property="field2" depends="email">        <arg0 key="error.field2" />      </field>    </form>  </formset></form-validation>

4.6. Client side validation

The support of Commons Validator in Spring Modules provides too the possibility to use a client side validation. It provides a dedicated taglib to generate the validation javascript code. To use this taglib, we firstly need to declare it at the beginnig of JSP files as following.

<%@ tglib uri="http://www.springmodules.org/tags/commons-validator" prefix="validator" %>

You need then to include the generated javascript code in the JSP file as following by using the javascript tag.

<validator:javascript formName="account"     staticJavascript="false" xhtml="true" cdata="false"/>

At last, you need to set the onSubmit attribute on the form tag in order to trigger the validation on the submission of the form.

<form method="post" action="(...)" οnsubmit="return validateMyForm(this)">

Chapter 5. Caching

5.1. Introduction

The Caching Module provides a consistent abstraction for performing caching, delivering the following benefits.

  • Provides a consistent programming model across different caching APIs such as EHCache, JBoss Cache, Java Caching System (JCS) and OSCache.

  • Provides a unified, simpler, easier to use, API for programmatic use of caching services than most of these previously mentioned APIs.

  • Supports different strategies for declarative caching services.

  • The Caching Module may be easily extended to support additional cache providers.

5.2. Uses

Caching is frequently used to improve application performance. A good example is the caching of data retrieved from a database. Even though ORM frameworks such as iBATIS and Hibernate already provide built-in caching, the Caching Module can be useful when executing methods that perform heavy calculations, are time consuming, and/or are resource hungry.

Caching can be added to frameworks without inherent caching support, such as JDBC or Spring JDBC.

The Caching Module may be used to have more control over your caching provider.

5.3. Configuration

Caching and cache-flushing can be easily configured by following these steps.

  1. Set up the cache provider. Instead of imposing the use of a particular cache implementation, the Caching Module lets you choose a cache provider that best suites the needs of your project.

  2. Enable the caching services. The Caching Module provides two ways to enable caching services.

    1. Declarative caching services.

       

    2. Programmatic use (via a single interface, org.springmodules.cache.provider.CacheProviderFacade).

5.4. Cache Provider

The Caching Module provides a common interface that centralizes the interactions with the underlying cache provider. Each facade must implement the interface org.springmodules.cache.provider.CacheProviderFacade or subclass the template org.springmodules.cache.provider.AbstractCacheProviderFacade.

Each strategy has the following properties.

  1. cacheManager (required)

    A cache manager administrates the cache. In general, a cache manager should be able to:

     

    • Store objects in the cache.

    • Retrieve objects from the cache.

    • Remove objects from the cache.

    • Flush or invalidate one or more regions of the cache, or the whole cache (depending on the cache provider.)

     

    The Caching Module provides factories that allow setting up cache managers and ensure that the created cache managers are properly released and destroyed before the Spring application context is closed.

    • org.springmodules.cache.provider.jboss.JbossCacheManagerFactoryBean

     

    • org.springmodules.cache.provider.jcs.JcsManagerFactoryBean

     

    • org.springmodules.cache.provider.oscache.OsCacheManagerFactoryBean

    These factories have a common, optional property, configLocation, which can be any resource used for configuration of the cache manager, such as a file or class path resource.

  2. failQuietlyEnabled (optional)

    If true, any exception thrown at runtime by the cache manager will not be rethrown, allowing applications to continue running even if the caching services fail. The default value is false: any exception thrown by the cache manager will be propagated and eventually will stop the execution of the application.

     

  3. serializableFactory (optional)

    Some cache providers, like EHCache and JCS, can only store objects that implement the java.io.Serializable interface, which may be necessary when storing objects in the file system or replicating changes in the cache to different nodes in a cluster.

     

    Such requirement imposes a problem when we need to store in the cache objects that are not Serializable and we do not have control of, for example objects generated by JAXB.

     

    A possible solution could be to "force" serialization on such objects. This can be achieved with a org.springmodules.cache.serializable.SerializableFactory. The Caching Module currently provides one strategy, org.springmodules.cache.serializable.XStreamSerializableFactory, which uses XStream to

    • Serialize objects to XML before they are stored in the cache.

    • Create objects back from XML after being retrieved from the cache.

    This feature is disabled by default (the value of serializableFactory is null.)

     

5.4.1. EHCache

EHCache can be used as cache provider through the facade org.springmodules.cache.provider.ehcache.EhCacheFacade. It must have a net.sf.ehcache.CacheManager as the underlying cache manager.

 

<!--    The created cache manager is an instance of net.sf.ehcache.CacheManager--><bean id="cacheManager"  class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"></bean><bean id="cacheProviderFacade"  class="org.springmodules.cache.provider.ehcache.EhCacheFacade">  <property name="cacheManager" ref="cacheManager" /></bean>

 

For more details about using EHCache with Spring, please refer to this excellent article by Omar Irbouh.

5.4.2. JBoss Cache

JBoss Cache can be used as cache provider through the facade org.springmodules.cache.provider.jboss.JbossCacheFacade. It must have a org.jboss.cache.TreeCache as the underlying cache manager.

 

<!--   The created cache manager is a singleton instance of org.jboss.cache.TreeCache--><bean id="cacheManager"  class="org.springmodules.cache.provider.jboss.JbossCacheManagerFactoryBean">  <!-- Optional properties -->  <property name="configLocation" value="classpath:org/springmodules/samples/cache-service.xml" />                            </bean><bean id="cacheProviderFacade"  class="org.springmodules.cache.provider.jboss.JbossCacheFacade">  <property name="cacheManager" ref="cacheManager" /></bean>

 

5.4.3. Java Caching System (JCS)

JCS can be used as cache provider through the facade org.springmodules.cache.provider.jcs.JcsFacade. It must have a org.apache.jcs.engine.control.CompositeCacheManager as the underlying cache manager.

 

<!--   The created cache manager is a singleton instance of org.apache.jcs.engine.control.CompositeCacheManager--><bean id="cacheManager"  class="org.springmodules.cache.provider.jcs.JcsManagerFactoryBean">  <!-- Optional properties -->  <property name="configLocation" value="classpath:org/springmodules/samples/jcs-config.properties" />                            </bean><bean id="cacheProviderFacade"  class="org.springmodules.cache.provider.jcs.JcsFacade">  <property name="cacheManager" ref="cacheManager" /></bean>

 

5.4.4. OSCache

OSCache can be used as cache provider through the facade org.springmodules.cache.provider.oscache.OsCacheFacade. It must have a com.opensymphony.oscache.general.GeneralCacheAdministrator as the underlying cache manager

 

<!--   The created cache manager is a singleton instance of   com.opensymphony.oscache.general.GeneralCacheAdministrator--><bean id="cacheManager"  class="org.springmodules.cache.provider.oscache.OsCacheManagerFactoryBean">  <!-- Optional properties -->  <property name="configLocation" value="classpath:org/springmodules/samples/oscache-config.properties" />                            </bean><bean id="cacheProviderFacade"  class="org.springmodules.cache.provider.oscache.OsCacheFacade">  <property name="cacheManager" ref="cacheManager" /></bean>

 

Rob Harrop posted an article explaining how to set up OSCache in Spring without using any factory.

5.5. Declarative Caching Services

The Caching Module offers declarative caching services powered by Spring AOP.

Declarative caching services offers a non-invasive solution, eliminating any dependencies on any cache implementation from your Java code.

The following sections describe the internal components common to the different strategies for declarative caching services.

5.5.1. Caching Advice

A caching advice applies caching to the return value of advised methods. It first checks that a value returned from a method call, with the same method arguments, is already stored in the cache. If a value is found, it will skip the method call and return the cached value. On the other hand, if the advice cannot find a cached value, it will proceed with the method call, store the return value of the call in the cache and finally return the new cached value.

Methods that do not have a return value (return value is void) are ignored, even if they were registered for aspect weaving.

5.5.2. Caching Models

Caching models encapsulate the rules to be followed by the caching advice when accessing the cache for object storage or retrieval. The Caching Module provides caching models for each of the supported cache providers:

  • org.springmodules.cache.provider.ehcache.EhCacheCachingModel specifies the name of the cache to use.

  • org.springmodules.cache.provider.jboss.JbossCacheCachingModel specifies the fully qualified name (FQN) of the node of the TreeCache to use.

  • org.springmodules.cache.provider.jcs.JcsCachingModel specifies the name of the cache and (optionally) the group to use.

  • org.springmodules.cache.provider.oscache.OsCacheCachingModel specifies the names of the groups to use, the cron expression to use to invalidate cache entries and the number of seconds that the object can stay in cache. All these properties are optional.

 

Caching advices can be configured to have caching models in a java.util.Map having each entry defined using standard Spring configuration:

<-- property of some caching advice --><property name="cachingModels">  <map>    <entry key="get*">      <bean class="org.springmodules.cache.provider.jcs.JcsCachingModel">        <property name="cacheName" value="someCache" />        <property name="group" value="someGroup" />      </bean>    </entry>  </map></property>

The type of caching model must match the chosen cache implementation: the example above must use Java Caching System (JCS) as the cache provider.

Caching advices can also have caching models as java.util.Properties resulting in a less verbose configuration:

<-- property of some caching advice --><property name="cachingModels">  <props>    <prop key="get*">cacheName=someCache;group=someGroup</prop>  </props></property>

 

The caching model has been defined as a String in the format propertyName1=propertyValue1;propertyName2=propertyValue2 which the Caching Module will automatically convert into a caching model using a PropertyEditor provided by the CacheProviderFacade.

The key of each entry is different for each declarative caching service strategy. More details will be provided in further sections.

5.5.3. Caching Listeners

An implementation of the interface org.springmodules.cache.interceptor.caching.CachingListener. A listener is notified when an object is stored in the cache. The Caching Module does not provide any implementation of this interface.

5.5.4. Key Generator

An implementation of org.springmodules.cache.key.CacheKeyGenerator. Generates the keys under which objects are stored in the cache. Only one implementation is provided, org.springmodules.cache.key.HashCodeCacheKeyGenerator, which creates keys based on the hash code of the object to store and a unique identifier.

5.5.5. Flushing Advice

A flushing advice flushes one or more groups of the cache, or the whole cache (depending on the cache provider) before or after an advised method is executed.

5.5.6. Flushing Models

Similar to caching models. Flushing models encapsulate the rules to be followed by the flushing advice when accessing the cache for invalidation or flushing. The Caching Module provides flushing models for each of the supported cache providers:

  • org.springmodules.cache.provider.ehcache.EhCacheFlushingModel specifies which caches should be flushed.

  • org.springmodules.cache.provider.jboss.JbossCacheFlushingModel specifies the FQN of the nodes to be removed from the TreeCache.

  • org.springmodules.cache.provider.jcs.JcsFlushingModel specifies which groups in which caches should be flushed. If the a cache is specified without groups, the whole cache is flushed.

  • org.springmodules.cache.provider.oscache.OsCacheFlushingModel specifies which groups should be flushed. If none is specified, the whole cache is flushed.

 

Like caching advices, flushing advices can be configured to have flushing models in a java.util.Map:

<-- property of some Flushing advice --><property name="flushingModels">  <map>    <entry key="update*">      <bean class="org.springmodules.cache.provider.jcs.JcsFlushingModel">        <property name="cacheStructs">          <list>            <bean class="org.springmodules.cache.provider.jcs.JcsFlushingModel.CacheStruct">              <property name="cacheName" value="someCache" />              <property name="groups" value="group1,group2" />            </bean>          </list>        </property>      </bean>    </entry>  </map></property>

The type of flushing model must match the chosen cache implementation: the example above must use Java Caching System (JCS) as the cache provider.

Flushing advices can also have flushing models as java.util.Properties resulting in a less verbose configuration:

<-- property of some flushing advice --><property name="flushingModels">  <props>    <prop key="update*">cacheName=someCache;group=group1,group2</prop>  </props></property>

 

The flushing model has been defined as a String in the format propertyName1=propertyValue1;propertyName2=propertyValue2 which the Caching Module will automatically convert into a flushing model using a PropertyEditor provided by the CacheProviderFacade.

The key of each entry is different for each declarative caching service strategy. More details will be provided in further sections.

5.6. Strategies for Declarative Caching Services

The following sections describe the different strategies for declarative caching services provided by the Caching Module.

5.6.1. CacheProxyFactoryBean

A CacheProxyFactoryBean applies caching services to a single bean definition, performing aspect weaving using a NameMatchCachingInterceptor as caching advice and a NameMatchFlushingInterceptor as flushing advice.

 

<!-- Using a EHCache cache manager --><bean id="cacheProviderFacade" class="..." /><bean id="cacheableServiceTarget"  class="org.springmodules.cache.integration.CacheableServiceImpl">  <property name="names">    <list>      <value>Luke Skywalker</value>      <value>Leia Organa</value>    </list>  </property></bean><bean id="cacheableService"  class="org.springmodules.cache.interceptor.proxy.CacheProxyFactoryBean">  <property name="cacheProviderFacade" ref="cacheProviderFacade" />  <property name="cachingModels">    <props>      <prop key="get*">cacheName=testCache</prop>    </props>  </property>  <property name="flushingModels">    <props>      <prop key="update*">cacheNames=testCache</prop>    </props>  </property>  <property name="cachingListeners">    <list>      <ref bean="cachingListener" />    </list>  </property>  <property name="target" ref="cacheableServiceTarget" /></bean>

 

In the above example, cacheableServiceTarget is the advised or proxied object, i.e. the bean to apply caching services to.

The caching interceptor will use a NameMatchCachingModelSource to get the caching models defining the caching rules to be applied to specific methods of the proxied class. In our example, it will apply caching to the methods starting with the text "get."

In a similar way, the flushing interceptor will use a NameMatchFlushingModelSource to get the flushing models defining the flushing rules to be applied to specific methods of the proxied class. In our example, it will flush the cache "testCache" after executing the methods starting with the text "update."

5.6.2. Source-level Metadata-driven Autoproxy

Autoproxying is driven by metadata. This produces a similar programming model to Microsoft's .Net ServicedComponents. AOP proxies for caching services are created automatically for the beans containing source-level, caching metadata attributes. The Caching Module supports metadata provided by Commons-Attributes and JDK 1.5+ Annotations. Both approaches are very flexible, because metadata attributes are restricted to describe whether caching services should be applied instead of describing how caching should occur. The how is described in the Spring configuration file.

Setting up autoproxy is quite simple:

<bean id="autoproxy"  class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />

 

5.6.2.1. Jakarta Commons-Attributes

The attributes org.springmodules.cache.interceptor.caching.Cached and org.springmodules.cache.interceptor.flush.FlushCache are used to indicate that an interface, interface method, class, or class method should be target for caching services.

public class CacheableServiceImpl implements CacheableService {  /**   * @@org.springmodules.cache.interceptor.caching.Cached(modelId="testCaching")   */  public final String getName(int index) {    // some implementation  }  /**   * @@org.springmodules.cache.interceptor.flush.FlushCache(modelId="testFlushing")   */  public final void updateName(int index, String name) {    // some implementation  }}

 

Now we need to tell Spring to apply caching services to the beans having Commons-Attributes metadata:

<bean id="attributes"  class="org.springframework.metadata.commons.CommonsAttributes" /><bean id="cachingInterceptor"  class="org.springmodules.cache.interceptor.caching.MetadataCachingInterceptor">  <property name="attributes" ref="attributes" />  <property name="cacheProviderFacade" ref="cacheProviderFacade" />  <property name="cachingListeners">    <list>      <ref bean="cachingListener" />    </list>  </property>  <property name="cachingModels">    <props>      <prop key="testCaching">cacheName=testCache</prop>    </props>  </property></bean><bean id="cachingAttributeSourceAdvisor"  class="org.springmodules.cache.interceptor.caching.CachingAttributeSourceAdvisor">  <constructor-arg ref="cachingInterceptor" /></bean><bean id="flushingInterceptor"  class="org.springmodules.cache.interceptor.flush.MetadataFlushingInterceptor">  <property name="attributes" ref="attributes" />  <property name="cacheProviderFacade" ref="cacheProviderFacade" />  <property name="flushingModels">    <props>      <prop key="testFlushing">cacheNames=testCache</prop>    </props>  </property></bean><bean id="flushingAttributeSourceAdvisor"  class="org.springmodules.cache.interceptor.flush.FlushingAttributeSourceAdvisor">  <constructor-arg ref="flushingInterceptor" /></bean><!-- Set up the objects to apply caching to --><bean id="cacheableService"  class="org.springmodules.cache.integration.CacheableServiceImpl">  <property name="names">    <list>      <value>Luke Skywalker</value>      <value>Leia Organa</value>    </list>  </property></bean>

 

The property modelId of the metadata attribute Cached should match the id of a caching model configured in the caching advice (in our example the bean with id cachingInterceptor.) This way the caching advice will know which caching model should use and how caching should be applied. In the above example, the caching advice will store in the EHCache testCache the return value of the method getName.

The same matching mechanism is applied to flushing models. The property modelId of the metadata attribute FlushCache shoule match the id of a flushing model configured in the flushing advice (the bean with id flushingInterceptor.) The flushing advice will know which flushing model to use. In the above example, the EHCache testCache will be flushed after executing the method updateName.

Usage of Commons-Attributes requires an extra compilation step which generates the code necessary to access metadata attributes. Please refer to its documentation for more details.

5.6.2.2. JDK 1.5+ Annotations

Source-level metadata attributes can be declared using JDK 1.5+ Annotations:

public class TigerCacheableService implements CacheableService {  @Cacheable(modelId = "testCaching")  public final String getName(int index) {    // some implementation.  }  @CacheFlush(modelId = "testFlushing")  public final void updateName(int index, String name) {    // some implementation.  }}

 

The annotations org.springmodules.cache.annotations.Cacheable and org.springmodules.cache.annotations.CacheFlush work exactly the same as their Commons-Attributes counterparts. Configuration in the Spring context is also very similar:

<bean id="cachingAttributeSource"  class="org.springmodules.cache.annotations.AnnotationCachingAttributeSource"></bean><bean id="cachingInterceptor"  class="org.springmodules.cache.interceptor.caching.MetadataCachingInterceptor">  <property name="cacheProviderFacade" ref="cacheProviderFacade" />  <property name="cachingAttributeSource" ref="cachingAttributeSource" />  <property name="cachingListeners">    <list>      <ref bean="cachingListener" />    </list>  </property>  <property name="cachingModels">    <props>      <prop key="testCaching">cacheName=testCache</prop>    </props>  </property></bean><bean id="cachingAttributeSourceAdvisor"  class="org.springmodules.cache.interceptor.caching.CachingAttributeSourceAdvisor">  <constructor-arg ref="cachingInterceptor" /></bean><bean id="flushingAttributeSource"  class="org.springmodules.cache.annotations.AnnotationFlushingAttributeSource"></bean><bean id="flushingInterceptor"  class="org.springmodules.cache.interceptor.flush.MetadataFlushingInterceptor">  <property name="cacheProviderFacade" ref="cacheProviderFacade" />  <property name="flushingAttributeSource" ref="flushingAttributeSource" />  <property name="flushingModels">    <props>      <prop key="testFlushing">cacheNames=testCache</prop>    </props>  </property></bean><bean id="flushingAttributeSourceAdvisor"  class="org.springmodules.cache.interceptor.flush.FlushingAttributeSourceAdvisor">  <constructor-arg ref="flushingInterceptor" /></bean> <!-- Set up the objects to apply caching to --><bean id="cacheableService"  class="org.springmodules.cache.annotations.TigerCacheableService">  <property name="names">    <list>      <value>Luke Skywalker</value>      <value>Leia Organa</value>    </list>  </property></bean>          

 

By using JDK 1.5+ Annotations, we don't need the extra compilation step (required by Commons-Attributes.) The only downside is we can not use Annotations with JDK 1.4.

5.6.3. BeanNameAutoProxyCreator

 

<bean id="cachingInterceptor"  class="org.springmodules.cache.interceptor.caching.MethodMapCachingInterceptor">  <property name="cacheProviderFacade" ref="cacheProviderFacade" />  <property name="cachingListeners">    <list>      <ref bean="cachingListener" />    </list>  </property>  <property name="cachingModels">    <props>      <prop key="org.springmodules.cache.integration.CacheableService.get*">cacheName=testCache</prop>    </props>  </property></bean><bean id="flushingInterceptor"  class="org.springmodules.cache.interceptor.flush.MethodMapFlushingInterceptor">  <property name="cacheProviderFacade" ref="cacheProviderFacade" />  <property name="flushingModels">    <props>      <prop key="org.springmodules.cache.integration.CacheableService.update*">cacheNames=testCache</prop>    </props>  </property></bean><bean  class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">  <property name="beanNames">    <list>      <idref local="cacheableService"/>    </list>  </property>  <property name="interceptorNames">    <list>      <value>cachingInterceptor</value>      <value>flushingInterceptor</value>    </list>  </property></bean><bean id="cacheableService"  class="org.springmodules.cache.integration.CacheableServiceImpl">  <property name="names">    <list>      <value>Luke Skywalker</value>      <value>Leia Organa</value>    </list>  </property></bean>

 

Assuming that we already have a CacheProviderFacade instance in our ApplicationContext, the first thing we need to do is create the caching advice MethodMapCachingInterceptor and the flushing advice MethodMapFlushingInterceptor to use. AOP proxies are created for the objects which match the given fully qualified class name and method name (which accepts wildcards.)

Once we have the advices, we feed them to a BeanNameAutoProxyCreator along with the names of the beans in the ApplicationContext we want to apply caching services to.

5.7. Programmatic Use

First, we need to configure a org.springmodules.cache.provider.CacheProviderFacade in the Spring ApplicationContext (please refer to Section 5.4, “Cache Provider” for more details.) Then we need to obtain a reference to it and call any of this methods from our Java code:

void cancelCacheUpdate(Serializable key) throws CacheException;void flushCache(FlushingModel model) throws CacheException;Object getFromCache(Serializable key, CachingModel model) throws CacheException;boolean isFailQuietlyEnabled();void putInCache(Serializable key, CachingModel model, Object obj) throws CacheException;void removeFromCache(Serializable key, CachingModel model) throws CacheException;

 

Chapter 6. Java Content Repository (JSR-170) Support

6.1. Introduction

JSR-170 defines "a standard, implementation independent, way to access content bi-directionally on a granular level within a content repository. A Content Repository is a high-level information management system that is a superset of traditional data repositories. A content repository implements "content services" such as: author based versioning, full textual searching, fine grained access control, content categorization and content event monitoring. It is these "content services" that differentiate a Content Repository from a Data Repository." (taken from the JSR-170 description page).

More information about Java Content Repository (from here on refered as JCR) can be found at here.

The package has been designed to resemble as much as possible the ORM packages from the main Spring distribution. Users familiar with these can start using the JCR-support right away without much hassle; the documentation resembles the main documentation structure also. For those who haven't used them, please refer to the main Spring documentation, mainly chapter 12 (Data Access using O/R Mappers) as the current documentation focuses on the JCR specific details, the Spring infrastructure being outside the scope of this document. As the ORM package, the main reason for the JCR support is to ease development using Spring unchecked DAOexception hierarchy, integrated transaction management, ease of testing.

Before going any further I would like to thank Guillaume Bort <guillaume.bort@zenexity.fr> and Brian Moseley <bcm@osafoundation.org> which worked on some implementation of their own and were kind enough to provide their code and ideas when I started working on this package.

6.2. JSR standard support

The standard support works only with the JSR-170 API (represented by javax.jcr package) without making any use of specific features of the implementations (which we will discuss later).

6.2.1. SessionFactory

JSR-170 doesn't provide a notion of SessionFactory but rather a repository which based on the credentials and workspace provided returnes a session. The SessionFactory interface describes a basic contract for retrieving session without any knowledge of credentials, it's implementation acting as a wrapper around the javax.jcr.Repository:

<bean id="sessionFactory" class="org.springmodules.jcr.JcrSessionFactory"> <property name="repository" ref="repository"/></bean>

The only requirement for creating a sessionFactory is the repository (which will be discussed later). There are cases were credentials have to be submitted. One problem that new users have is that javax.jcr.SimpleCredentials requires a char array (char[]) as constructor parameter and not a String and the current Spring distribution (1.2.5) does not contains a PropertyEditor for char arrays. The following examples (taken from the sample) shows how we can use String statical methods to obtain a char array:

<bean id="sessionFactory" class="org.springmodules.jcr.JcrSessionFactory">  <property name="repository" ref="repository"/>  <property name="credentials">   <bean class="javax.jcr.SimpleCredentials">    <constructor-arg index="0" value="bogus"/>    <!-- create the credentials using a bean factory -->    <constructor-arg index="1">     <bean factory-bean="password"          factory-method="toCharArray"/>    </constructor-arg>   </bean>  </property>  <property></bean><!-- create the password to return it as a char[] --><bean id="password" class="java.lang.String">  <constructor-arg index="0" value="pass"/></bean>

Using the static toCharArray (from java.lang.String) we transformed the String supplied as password (with value 'pass') to SimpleCredentials for user 'bogus'. Note that JcrSessionFactory can also register namespaces, add listeners and has utility methods for determing the underlying repository properties - see the javadoc and the samples for more information.

6.2.2. Inversion of Control: JcrTemplate and JcrCallback

Most of the work with the JCR will be made through the JcrTemplate itself or through a JcrCallback. The template requires a SessionFactory and can be configured to create sessions on demand or reuse them (thread-bound) - the default behavior.

<bean id="jcrTemplate" class="org.springmodules.jcr.JcrTemplate">  <property name="sessionFactory" ref="sessionFactory"/>  <property name="allowCreate" value="true"/></bean>

JcrTemplate contains many of the operations defined in javax.jcr.Session and javax.jcr.query.Query classes plus some convenient ones; however there are cases when they are not enought. With JcrCallback, one can work directly with the Session, the callback begin thread-safe, opens/closes sessions and deals with exceptions:

    public void saveSmth() {        template.execute(new JcrCallback() {            public Object doInJcr(Session session) throws RepositoryException {                Node root = session.getRootNode();                log.info("starting from root node " + root);                Node sample = root.addNode("sample node");                sample.setProperty("sample property", "bla bla");                log.info("saved property " + sample);                session.save();                return null;            }        });    } 
6.2.2.1. Implementing Spring-based DAOs without callbacks

The developer can access the repository in a more 'traditional' way without using JcrTemplate (and JcrCallback) but still use Spring DAO exception hierarchy. SpringModules JcrDaoSupport offers base methods for retrieving Session from the SessionFactory (in a transaction-aware manner is transactions are supported) and for converting exceptions (which use SessionFactoryUtils static methods). Note that such code will usually pass "false" into getSession's the "allowCreate" flag, to enforce running within a transaction (which avoids the need to close the returned Session, as it its lifecycle is managed by the transaction):

public class ProductDaoImpl extends JcrDaoSupport {    public void saveSmth()            throws DataAccessException, MyException {        Session session = getSession(getSessionFactory(), false);        try {                Node root = session.getRootNode();                log.info("starting from root node " + root);                Node sample = root.addNode("sample node");                sample.setProperty("sample property", "bla bla");                log.info("saved property " + sample);                session.save();                return null;        }        catch (RepositoryException ex) {            throw convertJCRAccessException(ex);        }    }}

The major advantage of such direct JCR access code is that it allows any checked application exception to be thrown within the data access code, while JcrTemplate is restricted to unchecked exceptions within the callback. Note that one can often defer the corresponding checks and the throwing of application exceptions to after the callback, which still allows working with JcrTemplate. In general, JcrTemplate's convenience methods are simpler and more convenient for many scenarios.

6.2.3. RepositoryFactoryBean

Repository configuration have not been discussed by JSR-170 and every implementation has a different approach. The JCR-support provides an abstract repository factory bean which defined the main functionality leaving subclasses to deal only with the configuration issues. The current version supports jackrabbit and jeceira as repository implementations but adding new ones is very easy. Note that through Spring, one can configure a repository without the mentioned RepositoryFactoryBean.

6.3. Extensions support

JSR-170 defines 2 levels of complains and a number of optional features which can be provided by implementations transactions being one.

6.3.1. JackRabbit

JackRabbit is the default implementation of the JSR-170 and it's part of the Apache Foundation. Even though at the time of writing it's still part of the incubator, there are plans for releasing 1.0 in the near future. JackRabbit support both levels and all the optional features described in the specifications.

<bean id="repository" class="org.springmodules.jcr.jackrabbit.RepositoryFactoryBean">  <!-- normal factory beans params -->  <property name="configuration" value="classpath:jackrabbit-repo.xml"/>  <property name="homeDir" value="/repo"/></bean>

Note that RepositoryFactoryBean makes use of Spring Resource to find the configuration file.

6.3.1.1. Transaction Management

One of the nicest features of the JCR support in Spring modules is transaction management (find out more about Spring transaction management in Chapter 8 of the Spring official reference documentation. At the moment it's possible to use a LocalTransactionManager or use the JCA connector from JackRabbit to enlist the repository in a XA transaction through a JTA transaction manager. As a side note the JCA scenario can be used within an application server along with a specific descriptor or using a portable JCA connector (like Jencks) which can work outside or inside an application server.

6.3.1.1.1. LocalTransactionManager

For local transaction the LocalTransactionManager should be used:

<bean id="jcrTransactionManager" class="org.springmodules.jcr.jackrabbit.LocalTransactionManager">   <property name="sessionFactory" ref="jcrSessionFactory"/></bean>

for which only the session factory is required. As JackRabbit exposes a XA Resource the LocalTransactionManager creates a UserTransaction behinds the scenes and uses that for commiting/rolling back the changes.

and has to be set inside the application context or the default one (non trasactional aware one) will be used. Unfortunatelly implementation that support transactions don't have a standard way of exposing them - that's why this requirement. Basically all you have to do is configure the JcrTemplate for example like this:

<bean id="jcrTemplate" class="org.springmodules.jcr.JcrTemplate">  <property name="sessionFactory" ref="sessionFactory"/>  <property name="providerManager" ref="providerManager"/></bean>

Note that when using transactions in most cases you want to reuse the session (which means allowCreate property on jcrTemplate should be false (default)).

6.3.1.1.2. SessionHolderProviderManager and SessionHolderProvider

Because JSR-170 doesn't directly address transaction, details vary from repository to repository and results in various SessionHolderProvider implementations (this class is used internally by the JCR support classes (JcrAccessor subclasses, JcrInterceptor, OpenSessionInViewFilter/Interceptor). JCR support contains (quite a lot of) classes to make this issue as painful as possible. SessionHolderProviderManager acts as a registry of SessionHolderProviders for different repositories and has several implementations that return user defined provider or discover them automatically.

ServiceSessionHolderProvider is suitable for most of the cases. It uses JDK 1.3+ Service Provider specification (also known as META-INF/services) for determining the holder provider. The class looks on the classpath under META-INF/services for the file named "org.springmodules.jcr.SessionHolderProvider" (which contains the full qualified name of a SessionHolderProvider implementation). The providers found are instantiated and registered and later on used for the repository they support. Using the ServiceSessionHolderProvider is straight forward:

<bean id="providerManager" class="org.springmodules.jcr.support.ServiceSessionHolderProviderManager"/>
6.3.1.1.3. JTA transactions

For distributed transactions, using JCA is recommend in JackRabbit's case. An example is found inside the sample. You are free to use your application server JCA support; Jencks is used only for demonstrative purpose, the code inside the jackrabbit support having no dependency on it.

6.4. Mapping support

Working with the JCR resembles to some degree to working with JDBC. Mapping support for the JCR seems to be the next logical step but the software market doesn't seem to offer any mature solution. The current package offers support for jcr-mappingwhich is part of Graffito project which belongs to the Apache foundation. However, Graffito itself is still in the incubator and the jcr-mapping is described as a prototype. The current support provides some base functionality taken from a snapshot which can be found inside the distribtution which most probably is old. However as jcr-mapping is clearly in alpha stage and a work in progress, users should not invest too much in this area but are encouraged to experiment and provide feedback. The current support contains a JcrMappingCallback and Template plus a FactoryBean for creating MappingDescriptors (which allows using more then one mapping file which is not possible at the moment in the jcr-mapping project).

Chapter 7. Lucene

7.1. Introduction

According to the home page project, "Apache Lucene is a high-performance, full-featured text search engine library written entirely in Java. It is a technology suitable for nearly any application that requires full-text search, especially cross-platform".

The project is hosted by Apache. It allows to make scalable architecture based on distributed indexes and provides several kinds of indexes (in-memory, file-system based, database based).

Spring Modules offers a Lucene support to provide more flexibility in its APIs use. It adds some new abstractions to facilitate the management of IndexReader, IndexWriter and Searcher, the index locking and concurrent accesses, the query creation and the results extraction. It provides too facilities to index easily sets of files and database rows.

7.2. Indexing

7.2.1. IndexFactory

The Lucene support introduces a new abstraction to hide the getting of index reader and writer behind a factory. This abstraction is the IndexFactory class. It allows the support to be more flexible, to facilitate the use of design patterns and the index management.

public interface IndexFactory {  IndexReader getIndexReader();  IndexWriter getIndexWriter();}

7.2.2. Configuration

Spring Modules provides at this time only one index factory based on a directory and an analyzer. It provides too support for configuring several directory types.

7.2.2.1. Configuring directories

The base Lucene concept is directory: It represents the index. There are several index types and the Lucene support allow you to configure a RAM directory and a file system directory with dedicated FactoryBean.

To configure a RAM directory, you need to use the RAMDirectoryFactoryBean class.

<bean id="ramDirectory"      class="org.springmodules.lucene.index.support.RAMDirectoryFactoryBean"/>

Important note: Remember that a RAM directory is not a persistent index.

To configure a filesystem directory, you need to use the FSDirectoryFactoryBean class. The only mandatory property is the directory location of the index.

<bean id="fsDirectory"      class="org.springmodules.lucene.index.support.FSDirectoryFactoryBean">  <property name="location"><value>C:/temp/lucene</value></property></bean>

Important note: The sandbox of the Lucene project defines other directories like a database one. Spring Modules doesn't support these kind of directories at this time.

7.2.2.2. Configuring a SimpleIndexFactory

The SimpleIndexFactory class is the default factory to manipulate index. It is based on a Lucene directory and a default analyzer. To configure it in Spring, you need to use the corresponding SimpleIndexFactoryBean class.

<bean id="fsDirectory" class="org.springmodules.lucene.index.support.FSDirectoryFactoryBean">  <property name="location"><value>C:/temp/lucene</value></property></bean><bean id="indexFactory" class="org.springmodules.lucene.index.support.SimpleIndexFactoryBean">  <property name="directory"><ref local="fsDirectory" /></property>  <property name="analyzer">    <bean class="org.apache.lucene.analysis.SimpleAnalyzer"/>  </property></bean>

This factory is based on the IndexReaderFactoryUtils and IndexWriterFactoryUtils classes to manage the IndexReader and IndexWriter creation or getting. You must be aware that opening an index in a write mode will lock it until the writer is closed. Moreover some operations are forbidden between the reader and the writer (for example, a document delete using the reader and document addition using the writing).

For more informations, see the following section about the IndexFactory management.

7.2.3. Document type handling

The support provides a generic way to handle different file formats. It allows the indexer to be extended and supports other file formats. To make a new handler, we only need to implement the DocumentHandler interface which specifies the way to construct a Lucene document from an InputStream. You can pass informations about the source with the description parameter.

public interface DocumentHandler {  public Document getDocument(Map description,                      InputStream inputStream) throws IOException;}

The support provides too a dedicated manager with the DocumentHandlerManager interface and its default implementation, SimpleDocumentHandlerManager.

By default, the text handler is only registred in the manager but you can use the registration methods to add (registerDocumentHandler method) or remove (unregisterDocumentHandler method) one or more other handlers. You can get too the handler that match to a string with the getDocumentHandler method. If there is no matching handler, the method returns null.

public interface DocumentHandlerManager {  DocumentHandler getDocumentHandler(String name);  void registerDefautHandlers();  void registerDocumentHandler(DocumentMatching matching,                               FileDocumentHandler handler);  void unregisterDocumentHandler(DocumentMatching matching);}

As you can see in the method signatures, we need to use the DocumentMatching interface to specify when the handler can be used. It only defines a match method which takes a string as parameter. It could be a file name for example.

public interface DocumentMatching {  boolean match(String name);}

The support provides an implementation, DocumentExtensionMatching, that takes into account the file extensions. You only have to specify the extension that handler the class.

Note: This implementation is interesting for an indexing that works directly on files. If you work on streams, perhaps this isn't the implementation to use.

Endly, you can configure in Spring the handlers supporting by the manager with the dedicated FactoryBean, ExtensionDocumentHandlerManagerFactoryBean and then inject it in your components. Here is an example of configuration with file extension:

<bean id="documentHandlerManager" class=  "org.springmodules.lucene.index.object.file.ExtensionDocumentHandlerManagerFactoryBean">  <property name="documentHandlers">    <map>      <entry key="pdf">        <bean class=            "org.springmodules.samples.lucene.index.file.handlers.PdfBoxDocumentHandler"/>      </entry>      <entry key="rtf">        <bean class=         "org.springmodules.samples.lucene.index.file.handlers.DefaultRtfDocumentHandler"/>      </entry>      <entry key="xls">        <bean class=            "org.springmodules.samples.lucene.index.file.handlers.JExcelDocumentHandler"/>      </entry>      <entry key="doc">        <bean class=            "org.springmodules.samples.lucene.index.file.handlers.PoiWordDocumentHandler"/>      </entry>    </map>  </property></bean>

Important note: This manager can be used in both template and object approach.

7.2.4. Template approach

The Lucene support provides a template approach like Spring for JDBC, JMS... to manipulate index.

LuceneIndexTemplate is the central class of the Lucene support core package (org.springmodules.lucene.index.core) for the indexing. It simplifies the use of the corresponding Lucene APIs since it handles the creation and release of resources and allow you to configure declaratively the resource management. This helps to avoid common errors like forgetting to always close the index reader/writer. It executes the common operations on an index leaving application code the way to create or delete a document and questionwork on the index (numDoc property, optimization of an index, deleted documents...).

Important note: You must be aware that some operations have sense only when the index resources are shared between several method calls (hasDeletions, isDeleted, undeleteDocuments) and other are incompatible in the latter context (for example, add document and delete one when resources are shared between several calls).

Important note: This template can can have an index reader and writer opened at the same time.

The template uses the DocumentCreator and DocumentsCreator abstractions to create one or more Lucene documents that will be added to an index. For that, they respectively define a createDocument and createDocuments methods.

public interface DocumentCreator {  Document createDocument() throws IOException;}
public interface DocumentsCreator {  List createDocuments() throws IOException;}

If you need to create a Lucene Document from an InputStream, you must necessarily manage the stream outside the template class because the stream must be opened when it calls the addDocument method of the Lucene IndexWriter class. To allow the management of the InputStream by the template, the support introduces the InputStreamDocumentCreator interface. Its subclasses must implement the self-explanatory createInputStream and createDocumentFromInputStream methods.

public interface InputStreamDocumentCreator {  public InputStream createInputStream() throws IOException;  public Document createDocumentFromInputStream(                InputStream inputStream) throws IOException;}

An interesting implementation is the InputStreamDocumentCreatorWithManager abstract class which allows to delegate the document creation to a DocumentHandler for the resource. Its subclasses must implement the getResourceName method to allow the DocumentHandlerManager to select the right DocumentHandler and the getResourceDescription method to specify its mandatory fields.

public abstract class InputStreamDocumentCreatorWithManager                                implements InputStreamDocumentCreator {  public InputStreamDocumentCreatorWithManager(                  DocumentHandlerManager documentHandlerManager) { ... }  protected abstract String getResourceName();  protected abstract Map getResourceDescription();  public final Document createDocumentFromInputStream(                     InputStream inputStream) throws IOException { ... }}

The template provides several methods to manipulate an index.

public class LuceneIndexTemplate {  //...  public void deleteDocument(int internalDocumentId) { ... }  public void deleteDocument(Term term) { ... }  public void undeleteDocuments() { ... }  public boolean isDeleted(int internalDocumentId) { ... }  public boolean hasDeletions() { ... }  public void flushDeletes() { ... }  public int getMaxDoc() { ... }  public int getNumDocs() { ... }  public void addDocument(Document document) { ... }  public void addDocument(Document document,Analyzer analyzer) { ... }  public void addDocument(DocumentCreator creator) { ... }  public void addDocument(DocumentCreator creator,Analyzer analyzer) { ... }  public void addDocument(InputStreamDocumentCreator creator) { ... }  public void addDocument(InputStreamDocumentCreator creator,Analyzer analyzer) { ... }  public void addDocuments(List documents) { ... }  public void addDocuments(List documents,Analyzer) { ... }  public void addDocuments(DocumentsCreator creator) { ... }  public void addDocuments(DocumentsCreator creator,Analyzer) { ... }  public void optimize() { ... }  //...}

Here is a summary table to show which Lucene resource is used by a template method:

Table 7.1. Resource used by the template methods

LuceneIndexTemplate method signatureCorresponding resource used
void deleteDocument(int)IndexReader
void deleteDocument(Term)IndexReader
void undeleteDocuments()IndexReader
boolean isDeleted(int)IndexReader
boolean hasDeletions()IndexReader
void flushDeletes()IndexReader
int getMaxDoc()IndexReader
int getNumDocs()IndexReader
addDocument methodsIndexWriter
addDocuments methodsIndexWriter
void optimize()IndexWriter

Here is a using example of the LuceneIndexTemplate class use with both the DocumentCreator and InputStreamDocumentCreator interfaces:

public class IndexAccessorImpl extends LuceneIndexSupport implements IndexAccessor {  public void addDocument(final String text) {    getTemplate().addDocument(new DocumentCreator() {      public Document createDocument() throws IOException {        Document document = new Document();        document.add(Field.UnStored("contents", text));        document.add(Field.Keyword("type", "text"));        document.add(Field.Keyword("filename", title));        return document;    });  }  public void addDocument(final File file) {    getTemplate().addDocument(new InputStreamDocumentCreatorWithManager(                                                getDocumentHandlerManager()) {      public InputStream createInputStream() throws IOException {        return new FileInputStream(file);      }      protected String getResourceName() {        return file.getPath();      }      protected Map getResourceDescription() {        Map description=new HashMap();        description.put(DocumentHandler.FILENAME,file.getPath());        return description;      }    });  }}

and the corresponding Spring configuration:

<bean id="fsDirectory"      class="org.springmodules.lucene.index.support.FSDirectoryFactoryBean">  <property name="location"><value>C:/lucene/index</value></property></bean><bean id="indexFactory"      class="org.springmodules.lucene.index.support.SimpleIndexFactoryBean">  <property name="directory"><ref local="fsDirectory"/></property>  <property name="analyzer">    <bean class="org.apache.lucene.analysis.SimpleAnalyzer"/>  </property></bean><bean id="indexAccessor"      class="org.springmodules.samples.lucene.index.service.IndexAccessorImpl">  <property name="indexFactory"><ref local="indexFactory"/></property></bean>

Finally the index template provides you two callbacks to work directly on a IndexReader or IndexWriter instance. These callback are based on the ReaderCallBack interface:

public interface ReaderCallback {  Object doWithReader(IndexReader reader) throws IOException;}

and the WriterCallback interface:

public interface WriterCallback {  Object doWithWriter(IndexWriter writer) throws IOException;}

and are used by two dedicated methods of the template:

public class LuceneIndexTemplate {  //...  public void read(ReaderCallback callback) { ... }  public void write(WriterCallback callback) { ... }  // ...}

7.2.5. Mass indexing approach

The support offers facilities to index an important number of documents or datas from a directory (or a set of directory) or a database. It is divided into two parts:

  • Indexing a directory and its sub directories recursively. This approach allows you to register custom handlers to index several file types.

  • Indexing a database. This approach allows you to specify the SQL requests in order to get the datas to index. A callback is then provided to create a Lucene document from a ResultSet. this feature is based on the Spring JDBC framework.

Every classes of this approach are located in the org.springmodules.lucene.index.object package and its sub packages.

7.2.5.1. Indexing directories

Indexing directories is implemented by the DirectoryIndexer class. To use it, you simply call its index method which needs the base directory. This class will browse this directory and all its sub directories, and tries to index every files which have a dedicated handler.

public class DirectoryIndexer extends AbstractIndexer {  //...  public void index(String dirToParse) { ... }  public void index(String dirToParse,boolean optimizeIndex) { ... }  //...}

Important note: If you set the optimizeIndex parameter as true, the index will be optimized after the indexing.

This class is based on a mechanism to handle different file types. It uses the DocumentHandlerManager interface seen in the previous section. It allows the indexer to be extended and supports other file formats.

You can add too listeners to be aware of directories and files processing. In this case, you only need to implement the DocumentIndexingListener on which different methods will be called during the indexing. So the implementation will receive the following informations:

  • The indexer begins to handle all the files of a directory.

  • The indexer has ended to handle all the files of a directory

  • The indexing of a file begins.

  • The indexing of a file is succesful.

  • The indexing of a file has failed. The exception is provided to the callback.

  • The indexer haven't the specific handler for the file type.

public interface DocumentIndexingListener {  public void beforeIndexingDirectory(File file);  public void afterIndexingDirectory(File file);  public void beforeIndexingFile(File file);  public void afterIndexingFile(File file);  public void onErrorIndexingFile(File file,Exception ex);  public void onNotAvailableHandler(File file);}

To associate a listener with the indexer, you can simply use its addListener method and to remove one, the removeListener method.

public class DirectoryIndexer extends AbstractIndexer {  //...  public void addListener(DocumentIndexingListener listener) { ... }  public void removeListener(DocumentIndexingListener listener) { ... }  //...}

Here is an example of use:

public class SimpleDirectoryIndexingImpl                implements DirectoryIndexing,InitializingBean {  private IndexFactory indexFactory;  private DocumentHandlerManager documentHandlerManager;  private DirectoryIndexer indexer;  public SimpleDirectoryIndexingImpl() {}  public void afterPropertiesSet() throws Exception {    if( indexFactory!=null ) {      throw new IllegalArgumentException("indexFactory is required");    }    this.indexer=new DirectoryIndexer(indexFactory,documentHandlerManager);  }  public void indexDirectory(String directory) { indexer.index(directory,true); }  public void prepareListeners() {    DocumentIndexingListener listener=new DocumentIndexingListener() {      public void beforeIndexingDirectory(File file) {        System.out.println("Indexing the directory : "+file.getPath()+" ...");      }      public void afterIndexingDirectory(File file) {        System.out.println(" -> Directory indexed.");      }      public void beforeIndexingFile(File file) {        System.out.println("Indexing the file : "+file.getPath()+" ...");      }      public void afterIndexingFile(File file) {        System.out.println(" -> File indexed ("+duration+").");      }      public void onErrorIndexingFile(File file, Exception ex) {        System.out.println(" -> Error during the indexing : "+ex.getMessage());      }      public void onNotAvailableHandler(File file) {        System.out.println("No handler registred for the file : "+file.getPath()+" ...");      }    };    indexer.addListener(listener);  }  public IndexFactory getIndexFactory() { return indexFactory; }  public void setIndexFactory(IndexFactory factory) { indexFactory = factory; }  public DocumentHandlerManager getDocumentHandlerManager() {    return documentHandlerManager;  }  public void setDocumentHandlerManager(DocumentHandlerManager manager) {    documentHandlerManager = manager;  }}

and the corresponding Spring configuration:

<bean id="fsDirectory"      class="org.springmodules.lucene.index.support.FSDirectoryFactoryBean">  <property name="location"><value>C:/temp/lucene</value></property>    </bean><bean id="indexFactory"      class="org.springmodules.lucene.index.support.SimpleIndexFactoryBean">  <property name="directory"><ref local="fsDirectory" /></property>  <property name="analyzer">    <bean class="org.apache.lucene.analysis.SimpleAnalyzer"/>  </property></bean><bean id="documentHandlerManager" class= "org.springmodules.lucene.index.object.file.ExtensionDocumentHandlerManagerFactoryBean">  ...</bean><bean id="indexingDirectory"  class="org.springmodules.samples.lucene.index.console.SimpleDirectoryIndexingImpl">  <property name="indexFactory"><ref local="indexFactory"/></property>  <property name="documentHandlerManager">    <ref local="documentHandlerManager"/>  </property></bean>
7.2.5.2. Indexing databases

The support for the database indexing looks like the previous. It is implemented by the DatabaseIndexer class. To use it, you simply use its index method which needs the JDBC DataSource to use. This class will execute every sql requests registred, and tries to index every corresponding resultsets with the dedicated request handlers.

public class DatabaseIndexer extends AbstractIndexer {  //...  public void index(DataSource dataSource) { ... }  public void index(DataSource dataSource,boolean optimizeIndex) { ... }  //...}

Important note: If you set the optimizeIndex parameter as true, the index will be optimized after the indexing.

This class is based on a mechanism to handle different queries. It allows the indexer to execute every specified requests. To make a new handler, we only need to implement the SqlDocumentHandler interface which specifies the way to construct a Lucene document from a result set.

public interface SqlDocumentHandler {  public Document getDocument(SqlRequest request,ResultSet rs) throws SQLException;}

As you can see in the method signatures, we need to use the SqlRequest class to specify the SQL request to execute and its parameters. It defines two constructors according to the request (with or without parameters):

public class SqlRequest {  //...  public SqlRequest(String sql) { ... }  public SqlRequest(String sql,Object[] params,int[] types) { ... }  //...}

To add and remove requests, you can respectively use the registerDocumentHandler and unregisterDocumentHandler methods.

public class DatabaseIndexer extends AbstractIndexer {  //...  public void registerDocumentHandler(SqlRequest sqlRequest,                                      SqlDocumentHandler handler) { ... }  public void unregisterDocumentHandler(SqlRequest sqlRequest) { ... }  //...}

You can add too listeners to be aware of requests processing. In this case, you only need to implement the DatabaseIndexingListener on which different methods will be called during the indexing. So the implementation will receive the following informations:

  • The indexing of a request begins.

  • The indexing of a request is succesful.

  • The indexing of a request has failed. The exception is provided to the callback.

public interface DatabaseIndexingListener {  public void beforeIndexingRequest(SqlRequest request);  public void afterIndexingRequest(SqlRequest request);  public void onErrorIndexingRequest(SqlRequest request,Exception ex);}

To associate a listener with the indexer, you can simply use its addListener method.

public class DatabaseIndexer extends AbstractIndexer {  //...  public void addListener(DatabaseIndexingListener listener) { ... }  public void removeListener(DatabaseIndexingListener listener) { ... }  //...}

Here is an example of use:

public class SimpleDatabaseIndexingImpl                  implements DatabaseIndexing,InitializingBean {  private DataSource dataSource;  private IndexFactory indexFactory;  private DatabaseIndexer indexer;  public SimpleDatabaseIndexingImpl() {}  public void afterPropertiesSet() throws Exception {    if( indexFactory!=null ) {      throw new IllegalArgumentException("indexFactory is required");    }    this.indexer=new DatabaseIndexer(indexFactory);  }  public void prepareDatabaseHandlers() {    //Register the request handler for book_page table without parameters    this.indexer.registerDocumentHandler(            new SqlRequest("select book_page_text from book_page"),            new SqlDocumentHandler() {      public Document getDocument(SqlRequest request,                                  ResultSet rs) throws SQLException {        Document document=new Document();        document.add(Field.Text("contents", rs.getString("book_page_text")));        document.add(Field.Keyword("request", request.getSql()));        return document;      }    });    //Register the request handler for book_page table with parameters    /*this.indexer.registerDocumentHandler(           new SqlRequest("select book_page_text from book_page where book_id=?",                        new Object[] {new Integer(1)},new int[] {Types.INTEGER}),           new SqlDocumentHandler() {      public Document getDocument(SqlRequest request,                                  ResultSet rs) throws SQLException {         Document document=new Document();         document.add(Field.Text("contents", rs.getString("book_page_text")));         document.add(Field.Keyword("request", sql));         return document;       }    });*/  }  public void indexDatabase() {    indexer.index(dataSource,true);  }  public void prepareListeners() {    DatabaseIndexingListener listener=new DatabaseIndexingListener() {      public void beforeIndexingRequest(SqlRequest request) {        System.out.println("Indexing the request : "+request.getSql()+" ...");      }      public void afterIndexingRequest(SqlRequest request) {        System.out.println(" -> request indexed.");      }      public void onErrorIndexingRequest(SqlRequest request, Exception ex) {        System.out.println(" -> Error during the indexing : "+ex.getMessage());      }    };    indexer.addListener(listener);  }  public IndexFactory getIndexFactory() { return indexFactory; }  public void setIndexFactory(IndexFactory factory) { indexFactory = factory; }  public DataSource getDataSource() { return dataSource; }  public void setDataSource(DataSource source) { dataSource = source; }}

and the corresponding Spring configuration:

<bean id="dataSource"      class="org.springframework.jdbc.datasource.DriverManagerDataSource">  <property name="driverClassName">    <value>org.hsqldb.jdbcDriver</value>  </property>  <property name="url">    <value>jdbc:hsqldb:hsql://localhost:9001</value>  </property>  <property name="username"><value>sa</value></property>  <property name="password"><value/></property></bean><bean id="fsDirectory"      class="org.springmodules.lucene.index.support.FSDirectoryFactoryBean">  <property name="location"><value>C:/temp/lucene</value></property></bean><bean id="indexFactory"      class="org.springmodules.lucene.index.support.SimpleIndexFactoryBean">  <property name="directory"><ref local="fsDirectory" /></property>  <property name="analyzer">    <bean class="org.apache.lucene.analysis.SimpleAnalyzer"/>  </property></bean><bean id="indexingDatabase"   class="org.springmodules.samples.lucene.index.console.SimpleDatabaseIndexingImpl">  <property name="indexFactory"><ref local="indexFactory"/></property>  <property name="dataSource"><ref local="dataSource"/></property></bean>

[TODO: indexing of blob database row field]

7.2.6. IndexFactory management

Spring Modules provides support to keep opened IndexReader and/or IndexWriter between several calls using the resources. Therefore, some template methods are useful only in a context where resources remain opened between several calls (hasDeletions, isDeleted, undeleteAll methods).

By default, every classes using IndexFactory create a new IndexReader or IndexWriter according to their features and close them before the end of the calls. So it prevents to remain the index reader and writer opened during a long time.

However, you can overwrite this mechanism using a dedicated template or interceptor to bind these resources in a ThreadLocal. Every IndexFactory will check this ThreadLocal to determine the resource to use (a new or one yet opened).

[TO FINISH]

7.3. Search

7.3.1. SearchFactory

The Lucene support introduces a new abstraction to hide the getting of index searchers in a dedicated factory. This abstraction is the SearcherFactory class. It allows the support to be more flexible and configure directly the used Searcher in Spring. The factory instance will be then injected in every component to make searchs across one or several indexes.

public interface SearcherFactory {  Searcher getSearcher() throws IOException;}

Note: If you need to call methods only available in sub classes, you cn use the SearcherCallback and cast the searcher to this sub class. We describe the callback feature in a later section.

7.3.2. Configuration

Spring provides several factories to make searchs on a single index, on several indexes in a simple or parallel maner and on one or several remote indexes.

7.3.2.1. Configuring a SimpleSearcherFactory

This class is the simplest factory to get a searcher. This factory is only based on a single index.

<bean id="fsDirectory"      class="org.springmodules.lucene.index.support.FSDirectoryFactoryBean">  <property name="location"><value>C:/lucene/index1/</value></property></bean><bean id="searcherFactory"      class="org.springmodules.lucene.search.factory.SimpleSearcherFactory">  <property name="directory"><ref local="fsDirectory" /></property></bean>
7.3.2.2. Configuring a MultipleSearcherFactory

This class allows you to make searchs across several indexes. It is based on the Lucene MultiSearcher class.

<bean id="fsDirectory1"      class="org.springmodules.lucene.index.support.FSDirectoryFactoryBean">  <property name="location"><value>C:/lucene/index1/</value></property></bean><bean id="fsDirectory2"      class="org.springmodules.lucene.index.support.FSDirectoryFactoryBean">  <property name="location"><value>C:/lucene/index2/</value></property></bean><bean id="searcherFactory"      class="org.springmodules.lucene.search.factory.MultipleSearcherFactory">  <property name="directories">    <list>      <ref local="fsDirectory1"/>      <ref local="fsDirectory2"/>    </list>  </property></bean>
7.3.2.3. Configuring a ParallelMultipleSearcherFactory

This class allows you to make searchs across several indexes in a parallel manner. It is based on the Lucene ParallelMultiSearcher class.

<bean id="fsDirectory1"      class="org.springmodules.lucene.index.support.FSDirectoryFactoryBean">  <property name="location"><value>C:/lucene/index1/</value></property></bean><bean id="fsDirectory2"      class="org.springmodules.lucene.index.support.FSDirectoryFactoryBean">  <property name="location"><value>C:/lucene/index2/</value></property></bean><bean id="searcherFactory"   class="org.springmodules.lucene.search.factory.ParallelMultipleSearcherFactory">  <property name="directories">    <list>      <ref local="fsDirectory1"/>      <ref local="fsDirectory2"/>    </list>  </property></bean>
7.3.2.4. Configuring a remote SearcherFactory

[TODO]

7.3.3. Make a search

7.3.3.1. Template approach

The Lucene support provides a template approach like Spring for JDBC, JMS... to make searchs.

LuceneSearchTemplate is the central class of the Lucene support core package (org.springmodules.lucene.search.core) for the search. It simplifies the use of the corresponding Lucene APIs since it handles the creation and release of resources. This helps to avoid common errors like forgetting to always close the searcher. It executes the search leaving application code the way to create a search query and extract datas from results.

The template uses the QueryCreator abstraction to create a query. For that, it defines a createQuery method which must be implemented.

public interface QueryCreator {  public Query createQuery(Analyzer analyzer) throws ParseException;}

Note: If you don't inject an Analyzer instance in the template, this parameter will be null, but perhaps you don't need it to construct your query.

The support provides a ParsedQueryCreator implementation to help to construct a query based on a QueryParser or a MultiFieldQueryParser. It uses an inner class QueryParams to hold the document fields to use and the query string. This class is used at the query creation and must be created by the configureQuery method. If you need to configure the created query (for example with a call of the setBoost method), you must overwrite the setQueryProperties method which gives it as method parameter.

public abstract class ParsedQueryCreator implements QueryCreator {  public abstract QueryParams configureQuery();  protected void setQueryProperties(Query query) { }  public final Query createQuery(Analyzer analyzer)                                     throws ParseException { ... }}

The template provides several methods to make a search for different usages (use of Filter, Sort and HitCollector):

public class LuceneSearchTemplate {  //...  public List search(QueryCreator queryCreator,HitExtractor extractor) { ... }  public List search(QueryCreator queryCreator,                                   HitExtractor extractor,Filter filter) { ... }  public List search(QueryCreator queryCreator,                                       HitExtractor extractor,Sort sort) { ... }  public List search(QueryCreator queryCreator,                         HitExtractor extractor,Filter filter,Sort sort) { ... }  public void search(QueryCreator queryCreator,HitCollector results) { ... }  // ...}

The following example constructs a query (basing on the QueryParser class) to search a text in the "contents" property of indexed documents. Then it constructs SearchResult objects with the search results. These objects will be added in a list by the support.

Here is the service class using the search template:

public class SearchServiceImpl extends LuceneSearchSupport                                    implements SearchService {  public List search(final String textToSearch) {    List results=getTemplate().search(new ParsedQueryCreator() {      public QueryParams configureQuery() {        return new QueryParams("contents",textToSearch);      }    },new HitExtractor() {      public Object mapHit(int id, Document document, float score) {        return new SearchResult(document.get("filename"),score);      }    }  });  return results;  }}

and the corresponding Spring configuration:

<bean id="fsDirectory"      class="org.springmodules.lucene.index.support.FSDirectoryFactoryBean">  <property name="location"><value>C:/lucene/index</value></property></bean><bean id="searcherFactory"      class="org.springmodules.lucene.search.factory.SimpleSearcherFactory">  <property name="directory"><ref local="fsDirectory"/></property></bean><bean id="searchService"      class="org.springmodules.samples.lucene.searching.service.SearchServiceImpl">  <property name="searcherFactory"><ref bean="searcherFactory" /></property>  <property name="analyzer">    <bean class="org.apache.lucene.analysis.SimpleAnalyzer"/>  </property></bean>

Finally the search template provides you a callback to work directly on a Searcher instance. You can use this method to cast the Searcher to its current implementation and call methods that aren't available at the Searcher level. The callback is based on the SearcherCallback interface:

public interface SearcherCallback {  public Object doWithSearcher(Searcher searcher)                             throws IOException,ParseException;}

and is used by a dedicated search method of the template:

public class LuceneSearchTemplate {  //...  public Object search(SearcherCallback callback) { ... }  // ...}
7.3.3.2. Object approach

The Lucene support provides the conception of search queries as object. Every classes of this approach are based on the LuceneSearchTemplate class.

The base class is LuceneSearchQuery. It configures the template by allowing you to inject the SearcherFactory and Analyzer you want to use. As this class is abstract, you must implement the search method to specify the way to make your search and how handle the results.

public abstract class LuceneSearchQuery {  private LuceneSearchTemplate template = new LuceneSearchTemplate();  public LuceneSearchTemplate getTemplate() { ... }  public void setAnalyzer(Analyzer analyzer) { ... }  public void setSearcherFactory(SearcherFactory factory) { ... }  public abstract List search(String textToSearch);}

As this class is very generic, Spring Modules providers a simple sub class to help you to implement your search queries. The abstract SimpleLuceneSearchQuery class implements the search methods leaving you to construct the query and specify the way to extract the results.

public abstract class SimpleLuceneSearchQuery extends LuceneSearchQuery {  protected abstract Query constructSearchQuery(                             String textToSearch) throws ParseException;  protected abstract Object extractResultHit(int id,                                        Document document, float score);  public final List search(String textToSearch) { ... }}

Here is an example of use:

String textToSearch=...;LuceneSearchQuery query=new SimpleLuceneSearchQuery() {  protected abstract Query constructSearchQuery(                             String textToSearch) throws ParseException;    QueryParser parser=new QueryParser("contents",getAnalyzer());    return parser.parse(textToSearch);  }  protected abstract Object extractResultHit(int id,                               Document document, float score) {    return document.get("filename");  }};List results=query.search(textToSearch);

7.3.4. SearchFactory management

Spring Modules provides support to keep opened Searcher between several calls using the resource. This is useful to avoid several index openings during the execution of search queries.

By default, every classes using SearcherFactory create a new Searcher according to their configurations and close it before the end of the calls.

However, you can overwrite this mechanism using a dedicated template or interceptor to bind these resources in a ThreadLocal. Every SearcherFactory will check this ThreadLocal to determine the resource to use (a new or one yet opened).

[TO FINISH]

Chapter 8. OSWorkflow

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值