Over a million developers have joined DZone.

Adding Search to AppFuse with Compass

· Java Zone

Navigate the Maze of the End-User Experience and pick up this APM Essential guide, brought to you in partnership with CA Technologies

Over 5 years ago, I recognized that AppFuse needed to have a search feature and entered an issue in JIRA. Almost 4 years later, a Compass Tutorial was created and shortly after Shay Banon (Compass Founder), sent in a patch. From the message he sent me:

A quick breakdown of enabling search:

  1. Added Searchable annotations to the User and Address.
  2. Defined Compass bean, automatically scanning the model package for mapped searchable classes. It also automatically integrates with Spring transaction manager, and stores the index on the file system ([work dir]/target/test-index).
  3. Defined CompassTemplate (similar in concept to HibernateTemplate).
  4. Defined CompassSearchHelper. Really helps to perform search since it does pagination and so on.
  5. Defined CompassGps, basically it allows for index operation allowing to completely reindex the data from the database. JPA and Hiberante also automatically mirror changes done through their API to the index. iBatis uses AOP.

Fast forward 2 years and I finally found the time/desire to put a UI on the backend Compass implementation that Shay provided. Yes, I realize that Compass is being replaced by ElasticSearch. I may change to use ElasticSearch in the future; now that the search feature exists, I hope to see it evolve and improve.

Since Shay's patch integrated the necessary Spring beans for indexing and searching, the only thing I had to do was to implement the UI. Rather than having an "all objects" results page, I elected to implement it so you could search on an entity's list screen. I started with Spring MVC and added a search() method to the UserController:

@RequestMapping(method = RequestMethod.GET)
public ModelAndView handleRequest(@RequestParam(required = false, value = "q") String query) throws Exception {
if (query != null && !"".equals(query.trim())) {
return new ModelAndView("admin/userList", Constants.USER_LIST, search(query));
} else {
return new ModelAndView("admin/userList", Constants.USER_LIST, mgr.getUsers());

public List<User> search(String query) {
List<User> results = new ArrayList<User>();
CompassDetachedHits hits = compassTemplate.findWithDetach(query);
log.debug("No. of results for '" + query + "': " + hits.length());
for (int i = 0; i < hits.length(); i++) {
results.add((User) hits.data(i));
return results;

At first, I used compassTemplate.find(), but got an error because I wasn't using an OpenSessionInViewFilter. I decided to go with findWithDetach() and added the following search form to the top of the userList.jsp page:

<div id="search">
<form method="get" action="${ctx}/admin/users" id="searchForm">
<input type="text" size="20" name="q" id="query" value="${param.q}"
placeholder="Enter search terms"/>
<input type="submit" value="<fmt:message key="button.search"/>"/>

NOTE: I tried using HTML5's <input type="search">, but found Canoo WebTest doesn't support it.

Next, I wrote a unit test to verify everything worked as expected. I found I had to call compassGps.index() as part of my test to make sure my index was created and up-to-date.

public class UserControllerTest extends BaseControllerTestCase {
private CompassGps compassGps;
private UserController controller;

public void testSearch() throws Exception {
ModelAndView mav = controller.handleRequest("admin");
Map m = mav.getModel();
List results = (List) m.get(Constants.USER_LIST);
assertTrue(results.size() >= 1);
assertEquals("admin/userList", mav.getViewName());

After getting this working, I started integrating similar code into AppFuse's other web framework modules (Struts, JSF and Tapestry). When I was finished, they all looked pretty similar from a UI perspective.


<div id="search">
<form method="get" action="${ctx}/admin/users" id="searchForm">
<input type="text" size="20" name="q" id="query" value="${param.q}"
placeholder="Enter search terms..."/>
<input type="submit" value="<fmt:message key="button.search"/>"/>


<div id="search">
<h:form id="searchForm">
<h:inputText id="q" name="q" size="20" value="#{userList.query}"/>
<h:commandButton value="#{text['button.search']}" action="#{userList.search}"/>


<div id="search">
<t:form method="get" t:id="searchForm">
<t:textfield size="20" name="q" t:id="q"/>
<input t:type="submit" value="${message:button.search}"/>

One frustrating thing I found was that Tapestry doesn't support method="get" and AFAICT, neither does JSF 2. With JSF, I had to make my UserList bean session-scoped or the query parameter would be null when it listed the results. Tapestry took me the longest to implement, mainly because I had issues figuring out how it's easy-to-understand-once-you-know onSubmit() handlers worked and I had the proper @Property and @Persist annotations on my "q" property. This tutorial was the greatest help for me. Of course, now that it's all finished, the code looks pretty intuitive.

Feeling proud of myself for getting this working, I started integrating this feature into AppFuse's code generation and found I had to add quite a bit of code to the generated list pages/controllers.

So I went on a bike ride...

While riding, I thought of a much better solution and added the following search method to AppFuse's GenericManagerImpl.java. In the code I added to pages/controllers previously, I'd already refactored to use CompassSearchHelper and I continued to do so in the service layer implementation.

private CompassSearchHelper compass;

public List<T> search(String q, Class clazz) {
if (q == null || "".equals(q.trim())) {
return getAll();

List<T> results = new ArrayList<T>();

CompassSearchCommand command = new CompassSearchCommand(q);
CompassSearchResults compassResults = compass.search(command);
CompassHit[] hits = compassResults.getHits();

if (log.isDebugEnabled() && clazz != null) {
log.debug("Filtering by type: " + clazz.getName());

for (CompassHit hit : hits) {
if (clazz != null) {
if (hit.data().getClass().equals(clazz)) {
results.add((T) hit.data());
} else {
results.add((T) hit.data());

if (log.isDebugEnabled()) {
log.debug("Number of results for '" + q + "': " + results.size());

return results;

This greatly simplified my page/controller logic because now all I had to do was call manager.search(query, User.class) instead of doing the Compass login in the controller. Of course, it'd be great if I didn't have to pass in the Class to filter by object, but that's the nature of generics and type erasure.

Other things I learned along the way:

  • To index on startup, I added compassGps.index() to the StartupListener..
  • In unit tests that leveraged transactions around methods, I had to call compassGps.index() before any transactions started.
  • To scan multiple packages for searchable classes, I had to add a LocalCompassBeanPostProcessor.

But more than anything, I was reminded it always helps to take a bike ride when you don't like the design of your code. ;-)

This feature and many more will be in AppFuse 2.1, which I hope to finish by the end of the month. In the meantime, please feel free to try out the latest snapshot.

From http://raibledesigns.com/rd/entry/adding_search_to_appfuse

Thrive in the application economy with an APM model that is strategic. Be E.P.I.C. with CA APM.  Brought to you in partnership with CA Technologies.


Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}