/* vim: set ts=2 et sw=2 cindent fo=qroca: */ package com.globant.katari.search.domain; import org.apache.commons.lang.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; import org.compass.core.Compass; // import org.compass.core.CompassSession; // import org.compass.core.CompassTransaction; import org.compass.core.CompassHit; import org.compass.core.spi.InternalCompass; import org.compass.core.support.search.CompassSearchResults; import org.compass.core.support.search.CompassSearchCommand; import org.compass.core.support.search.CompassSearchHelper; import org.compass.gps.CompassGps; import com.globant.katari.core.security.SecureUrlAccessHelper; /** Provides the mechanism to find objects through a reverse text index. * * This is the main entry point to find objects indexed through compass. * * @author nira.amit@globant.com * * TODO Document the syntax of the query. */ public class IndexRepository { /** The class logger. */ private static Logger log = LoggerFactory.getLogger(IndexRepository.class); /** The GPS device for the compass object. * * It is never null. */ private CompassGps compassGps; /** A search-helper object for the compass search. * * It is never null. */ private CompassSearchHelper searchHelper; /** The compass object that the work will be delegated to. * * It is never null. */ private Compass compass; /** The adapters supplied by the different modules for searchable entities. * * There should be one for every searchable entity registered with compass. * It is never null. */ private List<SearchAdapter> adapters; /** The {@link SecureUrlAccessHelper} used to check if the user can see the * object to search for. * * It is never null. */ private final SecureUrlAccessHelper urlAccessHelper; /** Constructor, builds an index repository. * * @param gps the compass gps implementation, it cannot be null. * * @param helper the compass search helper, it cannot be null. * * @param theCompass the compass session factory, it cannot be null. * * @param theAdapters the adapters that adapt the compass result to the * SearchResultElement. It cannot be null. * * @param theUrlAccessHelper validates if the current user can access to the * given url. It cannot be null. */ public IndexRepository(final CompassGps gps, final CompassSearchHelper helper, final Compass theCompass, final List<SearchAdapter> theAdapters, final SecureUrlAccessHelper theUrlAccessHelper) { Validate.notNull(gps, "The compass gps implementation cannot be null."); Validate.notNull(helper, "The compass helper cannot be null."); Validate.notNull(theCompass, "The compass session factory cannot be null."); Validate.notNull(theAdapters, "The adapters cannot be null."); Validate.notNull(theUrlAccessHelper, "The url access helper cannot be null."); compassGps = gps; searchHelper = helper; compass = theCompass; adapters = theAdapters; urlAccessHelper = theUrlAccessHelper; } /** Recreate the index using the database information. * * The reindex process first deletes the index and then recreates it. So * reindexing cannot be done concurrently with queries or incremental * updates. This is a potentially very expensive operation. */ public void reIndex() { log.trace("Entering reIndex"); if (!compassGps.isRunning()) { throw new RuntimeException("Compass should have been started."); } compassGps.index(); log.trace("Leaving reIndex"); } /** Search in the compass index. * * @param query Query entered by the user. It cannot be null. * * @param pageNumber Page you want to get. 0 is the first page. * * @return the result of the query in the index. It never returns null. */ public SearchResult find(final String query, final int pageNumber) { log.trace("Entering find"); if (query.trim().length() == 0) { log.trace("Leaving find with no results"); return new SearchResult(); } log.debug("Building query ..."); StringBuilder viewUrls = new StringBuilder(); for (SearchAdapter adapter: adapters) { log.debug("Considering adapter for class {}, checking url {}", adapter.getAdaptedClass().toString(), adapter.getViewUrl()); if (urlAccessHelper.canAccessUrl(null, adapter.getViewUrl())) { log.debug("User can access url {}", adapter.getViewUrl()); if (viewUrls.length() != 0) { viewUrls.append(" OR "); } viewUrls.append("(alias:"); String alias = ((InternalCompass) compass).getMapping() .findRootMappingByClass(adapter.getAdaptedClass()).getAlias(); viewUrls.append(alias); viewUrls.append(")"); } } if (viewUrls.length() == 0) { // There is nothing I have access to. log.trace("Leaving find with no results"); return new SearchResult(); } // TODO: Check if the parens are balanced in query !!! String refinedQuery = "(" + query + ") AND (" + viewUrls.toString() + ")"; log.debug("Executing query {}.", refinedQuery); CompassSearchCommand searchCommand; searchCommand = new CompassSearchCommand(refinedQuery); searchCommand.setPage(pageNumber); CompassSearchResults compassResults = searchHelper.search(searchCommand); SearchResult result; /* Not documented in compass, but apparently, this is the correct way to * check if the result was empty. This was apparent after watching the * source for CompassSearchHelper. */ if (compassResults.getTotalHits() != 0) { result = new SearchResult(compassResults.getPages().length, convert(compassResults)); log.trace("Leaving find with {} hits", compassResults.getTotalHits()); return result; } else { result = new SearchResult(); log.trace("Leaving find with no results"); return result; } } // /** Adds a new object to the index. * * @param r object to be added. */ // public void addObject(final Object r) { CompassSession session = // compass.openSession(); CompassTransaction transaction = // session.beginLocalTransaction(); session.save(r); transaction.commit(); // session.close(); } // // /** Adds a new Collection of objects to the index. // * // * @param resources collection of objects to be added // */ // public void addObject(final Collection< ? > resources) { // CompassSession session = compass.openSession(); // CompassTransaction transaction = session.beginLocalTransaction(); // for (Object r : resources) { // session.save(r); // } // transaction.commit(); // session.close(); // } // // /** Deletes a object from the index. // * // * @param r object to be deleted // * // */ // public void removeObject(final Object r) { // CompassSession session = compass.openSession(); // CompassTransaction transaction = session.beginLocalTransaction(); // session.delete(r); // transaction.commit(); // session.close(); // } // // /** Deletes a Collection of objects from the index. // * // * @param resources objects to be deleted // */ // public void removeObject(final Collection< ? > resources) { // CompassSession session = compass.openSession(); // CompassTransaction transaction = session.beginLocalTransaction(); // for (Object r : resources) { // session.delete(r); // } // transaction.commit(); // session.close(); // } // // /** Gets the object from the index. // * // * @param alias the alias of the class // * // * @param id the id in the class // * // * @return the object specified // */ // public Object getObject(final String alias, final String id) { // CompassSession session = compass.openSession(); // CompassTransaction transaction = session.beginLocalTransaction(); // Object o = session.load(alias, id); // transaction.commit(); // session.close(); // return o; // } /* I finally decided that the creation of the SearchResultElement wrappers * will be in this class. It looked like it belonged here. */ /** Convert the results to a List of SearchResultElement objects. * * @param c the compass search result, it cannot be null. * * @return a collection of google like results. It never returns null. */ private List<SearchResultElement> convert(final CompassSearchResults c) { log.trace("Entering convert"); List<SearchResultElement> results = new ArrayList<SearchResultElement>(); for (CompassHit hit : c.getHits()) { results.add(convert(hit.getData(), hit.getScore())); } log.trace("Leaving convert"); return results; } /** Convert the object to a SearchResultElement object. * * @param object the result you want to convert. It cannet be null. * * @param score the score for this result. * * @return a SearchResultElement, never returns null. * * TODO What happens if there are no adapters? Now it is throwing an * exception. */ private SearchResultElement convert(final Object object, final float score) { Validate.notNull(object, "The object cannot be null"); for (SearchAdapter adapter : adapters) { if (object.getClass().equals(adapter.getAdaptedClass())) { return adapter.convert(object, score); } } throw new RuntimeException("Could not convert object."); } }