/** * Copyright (c) 2009--2014 Red Hat, Inc. * * This software is licensed to you under the GNU General Public License, * version 2 (GPLv2). There is NO WARRANTY for this software, express or * implied, including the implied warranties of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 * along with this software; if not, see * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. * * Red Hat trademarks are not licensed under GPLv2. No permission is * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ package com.redhat.rhn.frontend.action.channel; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; import redstone.xmlrpc.XmlRpcClient; import redstone.xmlrpc.XmlRpcFault; import com.redhat.rhn.common.conf.ConfigDefaults; import com.redhat.rhn.common.validator.ValidatorException; import com.redhat.rhn.domain.rhnpackage.PackageFactory; import com.redhat.rhn.frontend.action.BaseSearchAction; import com.redhat.rhn.frontend.dto.PackageOverview; import com.redhat.rhn.frontend.xmlrpc.SearchServerIndexException; import com.redhat.rhn.manager.channel.ChannelManager; /** * PackageSearchHelper * @version $Rev$ */ public class PackageSearchHelper { private static Logger log = Logger.getLogger(PackageSearchHelper.class); private PackageSearchHelper() { } /** * Will form a search request and send message to search server * * @param sessionId session id * @param searchString search string * @param mode mode as in name only, name description, name and summary, free form * @param selectedArches list of archs * @param relevantUserId user id to filter by if relevant or architecture search * server the user can see is subscribed to * @param fineGrained fine grained search * @param filterChannelId channel id to filter by if channel search * @param searchType type of search to do, one of "relevant", "channel", * "architecture", or "all" * @return List of PackageOverview objects * @throws XmlRpcFault bad communication with search server * @throws MalformedURLException possibly bad configuration for search server address * @throws SearchServerIndexException error executing query */ public static List<PackageOverview> performSearch(Long sessionId, String searchString, String mode, String[] selectedArches, Long relevantUserId, Boolean fineGrained, Long filterChannelId, String searchType) throws XmlRpcFault, MalformedURLException, SearchServerIndexException { log.info("Performing pkg search: " + searchString + ", " + mode); List<String> pkgArchLabels = null; if (selectedArches != null) { pkgArchLabels = ChannelManager.listCompatiblePackageArches(selectedArches); } // call search server XmlRpcClient client = new XmlRpcClient( ConfigDefaults.get().getSearchServerUrl(), true); List<Object> args = new ArrayList<Object>(); args.add(sessionId); args.add("package"); args.add(preprocessSearchString(searchString, mode, pkgArchLabels)); args.add(fineGrained); List results = (List)client.invoke("index.search", args); if (log.isDebugEnabled()) { log.debug("results = [" + results + "]"); } if (results.isEmpty()) { return Collections.emptyList(); } // need to make the search server results usable by database // so we can get the actual results we are to display to the user. // also save the names into a Set for later. List<Long> pids = new ArrayList<Long>(); Set<String> names = new HashSet<String>(); for (Object itemObject : results) { Map item = (Map) itemObject; names.add((String) item.get("name")); Long pid = new Long((String)item.get("id")); pids.add(pid); } List<String> arList = null; if (selectedArches != null) { arList = Arrays.asList(selectedArches); } // The database does not maintain the order of the where clause. // In order to maintain the ranking from the search server, we // need to reorder the database results to match. This will lead // to a better user experience. List<PackageOverview> unsorted = PackageFactory.packageSearch(pids, arList, relevantUserId, filterChannelId, searchType); List<PackageOverview> ordered = new ArrayList<PackageOverview>(); Map<Long, PackageOverview> pidToPackageMap = new HashMap<Long, PackageOverview>(); Set<String> namesSet = new HashSet<String>(); // Get a list of package names from PackageOverview, to verify against the // names returned by the search server. for (PackageOverview po : unsorted) { pidToPackageMap.put(po.getId(), po); namesSet.add(po.getPackageName()); } // We got an error looking up a package name, it is most likely caused // by the search server giving us data which doesn't map into what is // in our database. This could happen if the search indexes are formed // for a different database instance. if (!names.containsAll(namesSet)) { throw new SearchServerIndexException(); } // Iterate through in the order that the search server returned, add packages // to the return list in the order they appear in the search results. for (Object resultObject : results) { Map result = (Map) resultObject; Long pid = new Long((String)result.get("id")); if (pidToPackageMap.get(pid) != null) { ordered.add(pidToPackageMap.get(pid)); } } return ordered; } private static String preprocessSearchString(String searchstring, String mode, List<String> arches) { if (!BaseSearchAction.OPT_FREE_FORM.equals(mode) && searchstring.indexOf(':') > 0) { throw new ValidatorException("Can't use free form and field search."); } StringBuilder buf = new StringBuilder(searchstring.length()); String[] tokens = searchstring.split(" "); for (String s : tokens) { if (s.trim().equalsIgnoreCase("AND") || s.trim().equalsIgnoreCase("OR") || s.trim().equalsIgnoreCase("NOT")) { s = s.toUpperCase(); } buf.append(s); buf.append(" "); } // if we're passing in arches let's add them to the query StringBuilder archBuf = new StringBuilder(); if (arches != null && !arches.isEmpty()) { archBuf.append(" AND ("); for (String s : arches) { archBuf.append("arch:"); archBuf.append(s); archBuf.append(" "); } archBuf.append(")"); } String query = buf.toString().trim(); // when searching the name field, we also want to include the filename // field in case the user passed in version number. if (BaseSearchAction.OPT_NAME_AND_SUMMARY.equals(mode)) { return "(name:(" + query + ")^2 summary:(" + query + ") filename:(" + query + "))" + archBuf.toString(); } else if (BaseSearchAction.OPT_NAME_AND_DESC.equals(mode)) { return "(name:(" + query + ")^2 description:(" + query + ") filename:(" + query + "))" + archBuf.toString(); } else if (BaseSearchAction.OPT_NAME_ONLY.equals(mode)) { return "(name:(" + query + ")^2 filename:(" + query + "))" + archBuf.toString(); } // OPT_FREE_FORM send as is. return buf.toString(); } }