package no.dusken.aranea.control;
import no.dusken.aranea.model.AraneaObject;
import no.dusken.common.service.GenericService;
import org.compass.core.CompassHit;
import org.compass.core.engine.SearchEngineQueryParseException;
import org.compass.core.engine.spellcheck.SearchEngineSpellCheckManager;
import org.compass.core.engine.spellcheck.SearchEngineSpellCheckSuggestBuilder;
import org.compass.core.engine.spellcheck.SearchEngineSpellSuggestions;
import org.compass.core.support.search.CompassSearchCommand;
import org.compass.core.support.search.CompassSearchResults;
import org.compass.spring.web.mvc.CompassSearchController;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Marvin B. Lillehaug <lillehau@underdusken.no>
* This controller is supposed to take only type and search string.
* If some more advanced features like checking what fields was checked in the form,
* it has to bee done in a controller extending this one.
*/
public class BasicSearchController<T extends AraneaObject> extends CompassSearchController {
/**
* The type to search for. Should match the SearchableConstant named type.
* Example: <p/> if the following is in the model for article, this should
* be "article":
*
* @SearchableConstant(name = "type", values = {"article"})
*/
private String type;
/**
* Default result loader. Only needed when loadObjects is true
*/
private GenericService<T> genericService;
/**
* This can be used if objects of unrelated types need to be searched for.
* @see SearchController and its corresponding bean in aranea-servlet
*/
private Map<String, GenericService<? extends T>> resultLoaders
= new HashMap<String, GenericService<? extends T>>();
/**
* Should the objects be loaded from the database and put in a map called
* loadedMap? Default is false
*/
private Boolean loadObjects = false;
/**
* Is there some extra sting other than type that we want to include in the query?
* e.g. date, section ...
* AND or OR have to be included. the query becommes "AND type: Article *extraQueryPart*"
* So extraQueryPart have to be, say "AND Section:spit"
*/
private String extraQueryPart = "";
protected ModelAndView handle(HttpServletRequest request,
HttpServletResponse response, Object o, BindException e) throws Exception {
CompassSearchCommand searchCommand = (CompassSearchCommand) o;
String originalQuery = searchCommand.getQuery();
searchCommand.setQuery("(" + originalQuery + ") AND type:(" + type + ") " + extraQueryPart);
ModelAndView mav;
try {
mav = super.handle(request, response, searchCommand, e);
} catch (SearchEngineQueryParseException exception) {
// could not parse the query. Give the user some friendly feedback (should
// be done in the view)
Map<String, Object> map = new HashMap<String, Object>();
map.put("error", true);
searchCommand.setQuery(originalQuery);
map.put("command", searchCommand);
return new ModelAndView(getSearchView(), map);
}
SearchEngineSpellCheckManager sescm = this.getCompass().getSpellCheckManager();
SearchEngineSpellCheckSuggestBuilder sescsb = sescm.suggestBuilder(originalQuery);
SearchEngineSpellSuggestions suggestions = sescsb.suggest();
mav.getModel().put("spellsuggestions", suggestions.getSuggestions());
searchCommand.setQuery(originalQuery);
if (loadObjects) {
loadHits(mav);
}
return mav;
}
/**
* The data contained in the {@link CompassHit}s returned by the search
* might contain uninitialized data. This method uses an appropriate
* {@link GenericService} to load the entire object retrieved from the search.
*
* @param mav The {@link ModelAndView} in which the loaded instances are put.
*/
private void loadHits(ModelAndView mav) {
// load the hits
CompassSearchResults results = (CompassSearchResults) mav
.getModel().get("searchResults");
// this map is the connection between the ID and the fully
// loaded object
Map<Long, T> loadedMap = new HashMap<Long, T>();
if (results != null && results.getHits().length > 0) {
for (CompassHit hit : results.getHits()) {
T entity = (T) hit.getData();
String resultsClassName = entity.getClass().getSimpleName();
GenericService<? extends T> service = null;
if (resultLoaders != null
&& resultLoaders.containsKey(resultsClassName)) {
service = resultLoaders.get(resultsClassName);
} else {
service = genericService;
}
T loadedEntity = service.getEntity(entity.getID());
loadedMap.put(entity.getID(), loadedEntity);
}
mav.getModel().put("loadedMap", loadedMap);
}
}
@Required
public void setLoadObjects(Boolean loadObjects) {
this.loadObjects = loadObjects;
}
@Required
public void setGenericService(GenericService<T> genericService) {
this.genericService = genericService;
}
public void setType(String type) {
this.type = type;
}
public void setExtraQueryPart(String extraQueryPart) {
this.extraQueryPart = extraQueryPart;
}
public void setResultLoaders(Map<String, GenericService<? extends T>> resultLoaders) {
this.resultLoaders = resultLoaders;
}
}