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; } }