One of the innovations we have brought to Hibernate Search is an alternative way to define the mapping information: a programmatic API.
The traditional way to map an entity into Hibernate Search is to use annotations. And it's perfectly fine for 95% of the use cases. In some cases though, some people had had a need for a more dynamic approach:
- they use a metamodel to generate or customize what is indexed in their entities and need to reconfigure things either on redeployment or on the fly based on some contextual information.
- they ship a product to multiple customers that require some customization.
What people asked for: the XML Way(tm)
For a while, people with this requirement have asked for an XML format equivalent to what annotations could do. Now the problem with XML is that:
- it's very verbose in it's way to duplicate the structural information of your code
<class name="Address"> <property name="street1"> <field> <analyzer definition="ngram"/> </field> </property> <!-- ... --> </class>
- while XML itself is type-safe, XML editors are still close to stone age, and developers writing XML in notepad are unfortunately quite common
- even if XML is type-safe, one cannot refactor the Java code and expect to get compile time errors or even better automatic integrated refactoring. For example, if I rename Address to Location, I still need to remember to change this in my xml file
- and finally, dynamically generating an XML stream to cope with the dynamic reconfiguration use case is not what I would call an intuitive solution
So we took a different road.
What they get: a fluent programmatic API
Instead of writing the mapping in XML, let's write it in Java. And to make things easier let's use a fluent contextual API (have intuitive method names, only expose the relevant operations).
SearchMapping mapping = new SearchMapping(); mapping .analyzerDef( "ngram", StandardTokenizerFactory.class ) .filter( LowerCaseFilterFactory.class ) .filter( NGramFilterFactory.class ) .param( "minGramSize", "3" ) .param( "maxGramSize", "3" ) .entity(Address.class) .indexed() .property("addressId", METHOD) .documentId() .property("street1", METHOD) .field() .field() .name("street1_ngram") .analyzer("ngram") .property("country", METHOD) .indexedEmbedded() .property("movedIn", METHOD) .dateBridge(Resolution.DAY);
As you can see, it's very easy to figure out what is going on here. But something you cannot see in this example is that your IDE only offers the relevant methods contextually. For example, unless you have just declared a property(), you won't be able to add a field() to it. Likewise, you can set an analyzer on a field, only if you are defining a field. It's like the dynamic languages fluent APIs be better ;)
The next step is to associate the programmatic mapping object to the Hibernate configuration.
//in Hibernate native Configuration configuration = ...; configuration.setProperty( "hibernate.search.model_mapping", mapping ); SessionFactory factory = configuration.buildSessionFactory();
//in JPA Map<String,String> properties = new HashMap<String,String)(1); properties.put( "hibernate.search.model_mapping", mapping ); EntityManagerFactory emf = Persistence.createEntityManagerFactory( "userPU", properties );
And voila!
Extensibility
The beauty of this API is that it's very easy for XML fan boys to create their own XML schema descriptors and use the programmatic API when parsing the XML stream. More interestingly, an application can expose specific configuration options (via a simple configuration file, a UI or any other form) and use this configuration to customize the mapping programmatically.
Please give this API a try, tell us what works and what does not, we are still figuring out things to make it as awesome as possible :)
Download
Many thanks to Amin Mohammed-Coleman for taking my half done initiative and polishing it up.
You can get Hibernate Search 3.2 Beta 1 here, the complete API documentation is present in the distribution; chapter 4.4.