package nl.gertontenham.magnolia.templating.rendering.components.search; import info.magnolia.context.MgnlContext; import info.magnolia.jcr.util.PropertyUtil; import info.magnolia.rendering.model.RenderingModel; import info.magnolia.rendering.template.RenderableDefinition; import nl.gertontenham.magnolia.templating.functions.FoundationTemplatingFunctions; import nl.gertontenham.magnolia.templating.rendering.ResultsRenderingModel; import nl.gertontenham.magnolia.templating.search.SearchException; import nl.gertontenham.magnolia.templating.search.SearchResult; import nl.gertontenham.magnolia.templating.search.SearchService; import nl.gertontenham.magnolia.templating.utils.RenderingUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.jcr.Node; import javax.jcr.RepositoryException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; /** * Search results renderable definition which can be used as the modelClass for presenting search results in freemarker template. */ public class ResultsRenderableDefinition<RD extends RenderableDefinition> extends ResultsAreaRenderableDefinition<RD> implements ResultsRenderingModel<RD> { private static final Logger log = LoggerFactory.getLogger(ResultsRenderableDefinition.class); private static final String SEARCH_FUZZY_FACTOR = "~0.8"; private static final String DEFAULT_MAX_RESULTS_PER_PAGE = "10"; private static final String BASE_QUERY = "SELECT p.* FROM [%s] AS p " + "WHERE ISDESCENDANTNODE(p, '%s') %s ORDER BY %s"; private SearchResult searchResult; private SearchService jcrSearchService; @Inject public ResultsRenderableDefinition(Node content, RD definition, RenderingModel<?> parent, FoundationTemplatingFunctions templatingFunctions, RenderingUtils renderingUtils, SearchService jcrSearchService) { super(content, definition, parent, templatingFunctions, renderingUtils); this.jcrSearchService = jcrSearchService; } @Override public String execute() { if (content != null) { String uuid; try { uuid = content.getIdentifier(); // Fetch pre-executed search results searchResult = MgnlContext.getAttribute(ResultsAreaRenderableDefinition.SEARCH_RESULT_ATTRIBUTE_PREFIX + uuid); // Check if search result are available and remove from context for later executions of the results area if (searchResult != null) { MgnlContext.removeAttribute(ResultsAreaRenderableDefinition.SEARCH_RESULT_ATTRIBUTE_PREFIX + uuid); } } catch (RepositoryException e) { log.debug("Error obtaining uuid during execute of component model", e); } } // No search results, execute new search! if (searchResult == null) { executeSearch(); } return super.execute(); } public void executeSearch() { List<String> orderBy = new ArrayList<String>(0); // Order by score, descending is default orderBy.add("score()"); if (StringUtils.isNotBlank(getSearchTerm())) { final String jcrQuery = buildQuery("nt:base", getSearchPath(), getAdvancedSearchTermPredicate(), orderBy); log.debug("executeSearch: buildQuery result {}",jcrQuery); try { searchResult = jcrSearchService.search(jcrQuery, getPageNumber(), getMaxResultsPerPage()); } catch (SearchException e) { log.debug("Error search website content", e); } } //else { // searchResult = new SearchResult(); // searchResult.setTotalCount(0); // searchResult.setNumPages(0); //} } public int getTotalCount() { if (searchResult == null) { return 0; } return searchResult.getTotalCount(); } public String getSubtitle() { return PropertyUtil.getString(content, "subtitle", ""); } public SearchResult getSearchResult() { return searchResult; } /** * Build JCR query string. * * @param from JCR from clause eg. nt:base * @param searchPath Start path where to start searches in eg /home * @param customFilter JCR based where clause * @param orderBy JCR based order by (defaults to score() desc) * @return Full JCR query string */ private String buildQuery(String from, String searchPath, String customFilter, List<String> orderBy) { return String.format(BASE_QUERY, from, StringUtils.defaultIfEmpty(searchPath, "/"), customFilter, getOrderString(orderBy)); } /** * Provides the (advanced) jcr where clause (part) based on queryStr in url and several configuration settings. * * @return */ private String getAdvancedSearchTermPredicate() { String searchTermPredicate = StringUtils.EMPTY; //ResultsAreaRenderableDefinition resultsAreaModel = ((ResultsAreaRenderableDefinition)getParent()); if (StringUtils.isNotBlank(getSearchTerm())) { // Standard fuzzy predicate StringBuilder predicate = new StringBuilder(MessageFormat.format(" AND contains(p.*, ''{0}'') ", new Object[]{getLuceneSearchTermPredicate("^0.4")})); // // Boosting predicate // predicate.append(MessageFormat.format(" AND ( (contains(p.title, ''{0}'') AND p.[jcr:primaryType] = ''mgnl:page'')", new Object[]{ getLuceneSearchTermPredicate(SEARCH_BOOST_VERY_IMPORTANT_FACTOR)} )); // predicate.append( MessageFormat.format(" OR (contains(p.windowTitle, ''{0}'') AND p.[jcr:primaryType] = ''mgnl:page'') ", new Object[]{ getLuceneSearchTermPredicate(SEARCH_BOOST_VERY_IMPORTANT_FACTOR) } )); // predicate.append( MessageFormat.format(" OR (contains(p.abstract, ''{0}'') AND p.[jcr:primaryType] = ''mgnl:page'')", new Object[]{ getLuceneSearchTermPredicate(SEARCH_BOOST_MEDIUM_IMPORTANT_FACTOR) } )); // predicate.append( MessageFormat.format(" OR (contains(p.description, ''{0}'') AND p.[jcr:primaryType] = ''mgnl:page'')", new Object[]{ getLuceneSearchTermPredicate(SEARCH_BOOST_MEDIUM_IMPORTANT_FACTOR) } )); // predicate.append( MessageFormat.format(" OR contains(p.description, ''{0}'') ", new Object[]{ getLuceneSearchTermPredicate(SEARCH_BOOST_LESS_IMPORTANT_FACTOR) } )); // predicate.append( MessageFormat.format(" OR contains(p.fileDescription, ''{0}'') ", new Object[]{ getLuceneSearchTermPredicate(SEARCH_BOOST_VERY_IMPORTANT_FACTOR) } )); // predicate.append( MessageFormat.format(" OR contains(p.title, ''{0}'') ", new Object[]{ getLuceneSearchTermPredicate(SEARCH_BOOST_VERY_IMPORTANT_FACTOR) } )); // predicate.append( MessageFormat.format(" OR contains(p.link, ''{0}{1}'') )", new Object[]{ getSearchTerm(), SEARCH_BOOST_MIN_IMPORTANT_FACTOR} )); // // // Excluding pages predicate // predicate.append(" AND p.[mgnl:template] <> 'adyen-templating-kit:pages/overview'"); // predicate.append(" AND p.[mgnl:template] <> 'adyen-templating-kit:pages/redirect'"); searchTermPredicate = predicate.toString(); } return searchTermPredicate; } /** * Provides the jcr where clause (part) based on Lucene parameters, like boost factor and Fuzzy factor. * * @return */ private String getLuceneSearchTermPredicate(String boostFactor) { StringBuffer searchTermPredicate = new StringBuffer(); //ResultsAreaRenderableDefinition resultsAreaModel = ((ResultsAreaRenderableDefinition)getParent()); String[] searchTermArray = StringUtils.split(getSearchTerm()); for (int i = 0; i < searchTermArray.length; i++) { searchTermPredicate.append(searchTermArray[i]); searchTermPredicate.append(boostFactor); searchTermPredicate.append(SEARCH_FUZZY_FACTOR); searchTermPredicate.append(" "); } return StringUtils.trim(searchTermPredicate.toString()); } /** * Build up jcr Order by string * * @param orderBy List of order by statements like "score() desc" * @return JCR based order by string */ private String getOrderString(List<String> orderBy) { final String defaultOrderStr = "desc"; String orderStr = ""; //first iterate the order items int iteratorNum = 0; if (orderBy != null) { for (String item : orderBy) { orderStr += String.format("%s ", item); iteratorNum++; if (iteratorNum < orderBy.size()) { orderStr += ", "; } } } //at the end append the default order string orderStr = orderStr + defaultOrderStr; return orderStr; } /** * Get searchPath content property. If not set then "/" (root) is used. * * @return search path */ private String getSearchPath() { String searchPath = "/"; try { if (content.hasProperty("searchPath")) { searchPath = templatingFunctions.nodeById(content.getProperty("searchPath").getString()).getPath(); } } catch (Exception e) { log.info("no searchPath property set on content", e); } return searchPath; } public int getMaxResultsPerPage() { int maxResultsPerPage; try { String maxResultsStringValue = PropertyUtil.getString(content, "maxResultsPerPage", DEFAULT_MAX_RESULTS_PER_PAGE); maxResultsPerPage = Integer.parseInt(maxResultsStringValue); } catch (NumberFormatException e) { maxResultsPerPage = Integer.parseInt(DEFAULT_MAX_RESULTS_PER_PAGE); } return maxResultsPerPage; } }