Auto-Complete, suggestions for Magnolia CMS with JQuery/SolR Part 3/5 (Adding the Solr servlet logic to magnolia )

In this part we will add the servlet to Magnolia that will communicate with SolR and some JQuery script in  the search page.

To avoid confusion I will just explain what I did to layer up things a bit, the complete classes will be provided as attachment or on request, I did not decide yet.
First of all to be able to plug in multiple types of search providers, like Elastic Search, gsa, etc.. but as well to provide a provider delivering different functionality regrouped in classes, regarding the modelClass that is used I decided to use something like this:

extendedSearchRevisited

The servlet class will ask the SearchProviderFactory currents implementation which SearchService it can use for this specific purpose. To avoid too much information at the same time, let’s concentrate on the core logic that is behind.

First let us create the actual servlet, create a new class somewhere in your module package, I did it in the extended-search module which is the actual generic model that is the interface between Magnolia templates and the search logic of the chosen provider.

/**
* This file Copyright (c) 2013 Magnolia International
* Ltd. (http://www.magnolia-cms.com). All rights reserved.
*
*
* This program and the accompanying materials are made
* available under the terms of the Magnolia Network Agreement
* which accompanies this distribution, and is available at
* http://www.magnolia-cms.com/mna.html
*
* Any modifications to this file must keep this entire header
* intact.
*
*/
package info.magnolia.extendedsearch.logic.servlets;

import info.magnolia.extendedsearch.EsUtil;
import info.magnolia.extendedsearch.logic.SearchService;

import java.io.IOException;
import java.util.HashMap;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* A basic servlet that calls the current servlet searchservice for the configured provider.
* @author kdewitte
*
*/
public class SearchServlet extends HttpServlet {

private static final long serialVersionUID = 2086015103407924808L;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {

super.doPost(req, resp);
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {

HashMap map = new HashMap();
map.put("fields", req.getParameter("fields"));
map.put("type", req.getParameter("type"));
map.put("queryType", req.getParameter("queryType"));

SearchService svc = EsUtil.getProviderInstance(String.class);
String results = svc.search(req.getParameter("search"), map);
resp.getWriter().write(results);
}

}

The important but notwithstanding slightly obscure call here is SearchService svc = EsUtil.getProviderInstance(String.class), it actually contacts the current implementation of the registered SearchFactory, in this case SolR and gets the corresponding SearchService for the String.class modelClass, we could have add null as well, everything depends on the implemented factory method. So whatever which search logic there is behind it is pushed back in the implementation of the factory and separated in a specific SearchService.
The actual SearchService implementation is defined below.


/**
* This file Copyright (c) 2013 Magnolia International
* Ltd.  (http://www.magnolia-cms.com). All rights reserved.
*
*
* This program and the accompanying materials are made
* available under the terms of the Magnolia Network Agreement
* which accompanies this distribution, and is available at
* http://www.magnolia-cms.com/mna.html
*
* Any modifications to this file must keep this entire header
* intact.
*
*/
package info.magnolia.extendedsearch.solrsearchprovider.logic;
import info.magnolia.extendedsearch.logic.SearchService;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.SpellCheckResponse.Suggestion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;

/**
* search servlet.
*
* @author kdewitte
*
*/
public class ServletSolrSearchProvider extends SolrSearch implements
SearchService<String, String, Map<String, String>, Object> {

private static final Logger log = LoggerFactory
.getLogger(ServletSolrSearchProvider.class);

/** Do the search*/
public String search(String query, Map<String, String> searchAttributes) {

this.initSolrServer();

SolrQuery solrQuery = new SolrQuery();
log.info(searchAttributes.get("queryType"));
SolrResponseType type = SolrResponseType.valueOf(searchAttributes.get("queryType"));

checkFieldOrSetDefault(solrQuery, searchAttributes, "start", "1");
checkFieldOrSetDefault(solrQuery, searchAttributes, "rows", "6");

solrQuery.setQueryType(convertQueryType(type));

solrQuery.setQuery(query.replaceAll(":|/", " "));
solrQuery.setFields(checkFieldOrSetDefault(searchAttributes, "fields", "id,title,url,path,authorid,lastmodified,staticlink,abstract").split(","));
solrQuery.addFilterQuery(checkFieldOrSetDefault(searchAttributes,
"type", "*"));

QueryResponse response = null;
try {
response = this.solrServer.query(solrQuery);
} catch (SolrServerException e) {
log.error("Problem querying solr in magnolia's solr servlet",e.getMessage());
}
return pretreatResponse(type, response);
}
//these methods are not used here since they are used for pushing to the index
public boolean addUpdate(String workspace, Map<String, Object> things) {
// TODO Auto-generated method stub
return false;
}

public boolean delete(String workspace, Map<String, Object> things) {
// TODO Auto-generated method stub
return false;
}
//neither this one wich is used to get the fields available in teh scheme
public Collection<String> getSchemaFields() {
// TODO Auto-generated method stub
return null;
}

/**Here we enable dealing with different search types*/
private String convertQueryType(SolrResponseType type){
switch(type){
case NORMAL:
return "/select";
case SUGGEST:
return "/suggest";
default:
return "/select";
}
}

/** helper methods */
private String checkFieldOrSetDefault(Map<String, String> searchAttributes,
String field, String def) {
if (searchAttributes.containsKey(field)) {
return searchAttributes.get(field);
} else {
return def;
}
}

private void checkFieldOrSetDefault(SolrQuery solrQuery,
Map&lt;String, String&gt; searchAttributes, String field, String def) {
if (searchAttributes.containsKey(field)) {
solrQuery.setParam(field, searchAttributes.get(field));
} else {
solrQuery.setParam(field, def);
}
}
/** simplify the solr query and return it in JSON format */
private String pretreatResponse(SolrResponseType type,
QueryResponse response) {

Gson gson = new Gson();
String resp = null;
switch (type) {
case NORMAL:
resp = gson.toJson(response);
break;
case SUGGEST:
List<Object> results = new ArrayList<Object>();
if (response.getSpellCheckResponse().getCollatedResult() != null) {
results.add(response.getSpellCheckResponse()
.getCollatedResult());
}
for (Suggestion suggestion : response.getSpellCheckResponse()
.getSuggestions()) {
results.addAll(suggestion.getAlternatives());
}

resp = gson.toJson(results);
break;
default:
resp = gson.toJson(response);
}
return resp;
}

}

An important remark here is that you do not really need the extended search factory, you can just register the servlet take away the interface dependency, and register the above provider as a component through Guice as a Singleton, contacting the ServletSolrSearchProvider instance through the @inject notation.

To finish this post we will add the configuration of the servlet in magnolia here:

Go to configuration–>server–>filters–>servlets:

This means we will access the servlet on “/searchservlet/”

servlet_registration

Finally add this to the version handler of the module where you created the servlet class:

@Override
protected List<Task> getBasicInstallTasks(InstallContext installContext) {

final List<Task> basicInstallTasks = new ArrayList<Task>();
basicInstallTasks.add(new RegisterModuleServletsTask());
return basicInstallTasks;
}

Leave a comment