/* * Copyright 2004-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.compass.core.support.search; import org.compass.core.Compass; import org.compass.core.CompassCallback; import org.compass.core.CompassDetachedHits; import org.compass.core.CompassException; import org.compass.core.CompassHit; import org.compass.core.CompassHits; import org.compass.core.CompassQuery; import org.compass.core.CompassSession; import org.compass.core.CompassTemplate; import org.compass.core.util.StringUtils; /** * <p>A general Search Controller that perform the search operations. The seardch controller is * thread safe. * * <p>Will perform the search operation on the <code>Compass</code> instance using the query * supplied by the {@link org.compass.core.support.search.CompassSearchCommand}. * * <p>Pagination will be enabled if <code>pageSize</code> property is set on the controller, * as well as providing the <code>page</code> number property on the * {@link org.compass.core.support.search.CompassSearchCommand}. * * <p>The search controller provides several extension points, including * {@link #buildQuery(CompassSearchCommand,org.compass.core.CompassSession)}, * {@link #doProcessBeforeDetach(CompassSearchCommand,org.compass.core.CompassSession,org.compass.core.CompassHits,int,int)} * and {@link #doProcessAfterDetach(CompassSearchCommand,org.compass.core.CompassSession,org.compass.core.CompassDetachedHits)}. * * @author kimchy */ public class CompassSearchHelper { private CompassTemplate compassTemplate; private Integer pageSize; /** * Creates a new compass search helper based on Compass without pagination. * * @param compass The compass instance to use */ public CompassSearchHelper(Compass compass) { this(compass, null); } /** * Creates a new compass search helper based on Compass with pagination. * * @param compass The Compass instance * @param pageSize The page size */ public CompassSearchHelper(Compass compass, Integer pageSize) { this.compassTemplate = new CompassTemplate(compass); this.pageSize = pageSize; } public CompassSearchResults search(final CompassSearchCommand command) throws CompassException { if (!StringUtils.hasText(command.getQuery()) && command.getCompassQuery() == null) { return new CompassSearchResults(new CompassHit[0], 0, 0); } return (CompassSearchResults) compassTemplate.execute(new CompassCallback() { public Object doInCompass(CompassSession session) throws CompassException { return performSearch(command, session); } }); } public CompassSearchResults searchLocal(final CompassSearchCommand command) throws CompassException { if (!StringUtils.hasText(command.getQuery()) && command.getCompassQuery() == null) { return new CompassSearchResults(new CompassHit[0], 0, 0); } return (CompassSearchResults) compassTemplate.executeLocal(new CompassCallback() { public Object doInCompass(CompassSession session) throws CompassException { return performSearch(command, session); } }); } /** * Performs the actual search operation. If pageSize is set, will perform pagination using the * provided size, if not, will return all the hits. Also allows for several extensions points: * {@link #buildQuery(CompassSearchCommand,org.compass.core.CompassSession)}, * {@link #doProcessBeforeDetach(CompassSearchCommand,org.compass.core.CompassSession,org.compass.core.CompassHits,int,int)} * and {@link #doProcessAfterDetach(CompassSearchCommand,org.compass.core.CompassSession,org.compass.core.CompassDetachedHits)}. * * @param searchCommand The search command to perform the search * @param session CompassSession to execute the search with * @return search results */ protected CompassSearchResults performSearch(CompassSearchCommand searchCommand, CompassSession session) { long time = System.currentTimeMillis(); CompassQuery query = buildQuery(searchCommand, session); CompassHits hits = query.hits(); CompassDetachedHits detachedHits; CompassSearchResults.Page[] pages = null; if (pageSize == null) { doProcessBeforeDetach(searchCommand, session, hits, -1, -1); detachedHits = hits.detach(); doProcessAfterDetach(searchCommand, session, detachedHits); } else { int iPageSize = pageSize.intValue(); int page = 0; int hitsLength = hits.getLength(); if (searchCommand.getPage() != null) { page = searchCommand.getPage().intValue(); } int from = page * iPageSize; if (from > hits.getLength()) { // from can't be negative from = Math.max(0, hits.getLength() - iPageSize); doProcessBeforeDetach(searchCommand, session, hits, from, (hitsLength - from)); detachedHits = hits.detach(from, (hitsLength - from)); } else if ((from + iPageSize) > hitsLength) { doProcessBeforeDetach(searchCommand, session, hits, from, (hitsLength - from)); detachedHits = hits.detach(from, (hitsLength - from)); } else { doProcessBeforeDetach(searchCommand, session, hits, from, iPageSize); detachedHits = hits.detach(from, iPageSize); } doProcessAfterDetach(searchCommand, session, detachedHits); int numberOfPages = (int) Math.ceil((float) hitsLength / iPageSize); pages = new CompassSearchResults.Page[numberOfPages]; for (int i = 0; i < pages.length; i++) { pages[i] = new CompassSearchResults.Page(); pages[i].setFrom(i * iPageSize + 1); pages[i].setSize(iPageSize); pages[i].setTo((i + 1) * iPageSize); if (from >= (pages[i].getFrom() - 1) && from < pages[i].getTo()) { pages[i].setSelected(true); } else { pages[i].setSelected(false); } } if (numberOfPages > 0) { CompassSearchResults.Page lastPage = pages[numberOfPages - 1]; if (lastPage.getTo() > hitsLength) { lastPage.setSize(hitsLength - lastPage.getFrom() + 1); lastPage.setTo(hitsLength); } } } time = System.currentTimeMillis() - time; CompassSearchResults searchResults = new CompassSearchResults(detachedHits.getHits(), time, hits.length()); searchResults.setPages(pages); return searchResults; } /** * <p>Acts as an extension point for search controller that wish to build * different CompassQueries. Since the search command can hold either a * {@link org.compass.core.CompassQuery} or a query string, will first * check if the {@link org.compass.core.CompassQuery} is set, and if not, * will use the query search string. * * <p>The default implementation when query string is provided uses the session to create a query * builder and use the queryString option, i.e.: * <code>session.queryBuilder().queryString(searchCommand.getQuery().trim()).toQuery();</code>. * * <p>Some other interesting options might be to add sorting to the query, * adding other queries using a boolean query, or executing a different * query. */ protected CompassQuery buildQuery(CompassSearchCommand searchCommand, CompassSession session) { if (searchCommand.getCompassQuery() != null) { return searchCommand.getCompassQuery(); } return session.queryBuilder().queryString(searchCommand.getQuery().trim()).toQuery(); } /** * An option to perform any type of processing before the hits are detached. */ protected void doProcessBeforeDetach(CompassSearchCommand searchCommand, CompassSession session, CompassHits hits, int from, int size) { } /** * An option to perform any type of processing after the hits are detached. */ protected void doProcessAfterDetach(CompassSearchCommand searchCommand, CompassSession session, CompassDetachedHits hits) { } /** * Returns the page size for the pagination of the results. If not set, not * pagination will be used. */ public Integer getPageSize() { return pageSize; } /** * Sets the page size for the pagination of the results. If not set, not * pagination will be used. * * @param pageSize The page size for pagination of the results */ public void setPageSize(Integer pageSize) { this.pageSize = pageSize; } }