/** * 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.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang.StringUtils; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.DynaActionForm; import redstone.xmlrpc.XmlRpcFault; import com.redhat.rhn.domain.channel.ChannelArch; import com.redhat.rhn.frontend.action.BaseSearchAction; import com.redhat.rhn.frontend.dto.PackageOverview; import com.redhat.rhn.frontend.struts.RequestContext; import com.redhat.rhn.frontend.struts.RhnHelper; import com.redhat.rhn.frontend.xmlrpc.SearchServerIndexException; import com.redhat.rhn.manager.channel.ChannelManager; /** * PackageSearchAction * @version $Rev$ */ public class PackageSearchAction extends BaseSearchAction { protected ActionForward doExecute(HttpServletRequest request, ActionMapping mapping, DynaActionForm form) throws MalformedURLException, XmlRpcFault, SearchServerIndexException { RequestContext ctx = new RequestContext(request); String searchString = form.getString(SEARCH_STR); String viewmode = form.getString(VIEW_MODE); Boolean fineGrained = (Boolean) form.get(FINE_GRAINED); String searchCriteria = form.getString(WHERE_CRITERIA); String[] selectedArches = null; Long filterChannelId = null; boolean relevantFlag = false; // Default to relevant channels if no search criteria was specified if (searchCriteria == null || searchCriteria.equals("")) { searchCriteria = RELEVANT; } // Handle the radio button selection for channel filtering if (searchCriteria.equals(RELEVANT)) { relevantFlag = true; } else if (searchCriteria.equals(ARCHITECTURE)) { /* The search call will function as being scoped to architectures if the arch list isn't null. In order to actually get radio-button-like functionality we can't rely on the arch list coming in from the form to be null; the user may have selected an arch but *not* the radio button for arch. If we push off retrieving the arches until we know we want to use them, we can get the desired functionality described by the UI. */ selectedArches = form.getStrings(CHANNEL_ARCH); } else if (searchCriteria.equals(CHANNEL)) { String sChannelId = form.getString(CHANNEL_FILTER); filterChannelId = Long.parseLong(sChannelId); } List<Map<String, String>> searchOptions = buildSearchOptions(); List<Map<String, String>> channelArches = buildChannelArches(); // Load list of available channels to select as filter List<Map<String, Object>> allChannels = ChannelManager.allChannelsTree(ctx.getCurrentUser()); request.setAttribute(SEARCH_STR, searchString); request.setAttribute(VIEW_MODE, viewmode); request.setAttribute(SEARCH_OPT, searchOptions); request.setAttribute(CHANNEL_ARCHES, channelArches); request.setAttribute(CHANNEL_ARCH, selectedArches); request.setAttribute(ALL_CHANNELS, allChannels); request.setAttribute(CHANNEL_FILTER, form.getString(CHANNEL_FILTER)); request.setAttribute(RELEVANT, relevantFlag ? "yes" : "no"); request.setAttribute(FINE_GRAINED, fineGrained); // Default where to search criteria request.setAttribute(WHERE_CRITERIA, searchCriteria); if (!StringUtils.isBlank(searchString)) { List<PackageOverview> results = performSearch(ctx, searchString, viewmode, fineGrained, selectedArches, filterChannelId, relevantFlag, searchCriteria); request.setAttribute(RequestContext.PAGE_LIST, results != null ? results : Collections.emptyList()); } else { request.setAttribute(RequestContext.PAGE_LIST, Collections.emptyList()); } return mapping.findForward(RhnHelper.DEFAULT_FORWARD); } /** * Actually do the package-search desired * @param ctx incoming request context * @param searchString string we're going to search on * @param viewmode what kind-of search are we doing? * @param fineGrained exact or fuzzy? * @param selectedArches do we care about specific arches? * @param filterChannelId do we care about a specific channel? * @param relevantFlag do we only care about 'relevant to registered profiles"? * @param searchCriteria the type of search we are doing * @return list of package-overviews found * @throws XmlRpcFault bad communication with search server * @throws MalformedURLException possibly bad configuration for search server address */ public List<PackageOverview> performSearch(RequestContext ctx, String searchString, String viewmode, Boolean fineGrained, String[] selectedArches, Long filterChannelId, boolean relevantFlag, String searchCriteria) throws XmlRpcFault, MalformedURLException { List<PackageOverview> results = PackageSearchHelper.performSearch(ctx.getWebSession().getId(), searchString, viewmode, selectedArches, ctx.getCurrentUser() .getId(), fineGrained, filterChannelId, searchCriteria); // Perform any post-search logic that wasn't done by the search server results = removeDuplicateNames(results); return results; } /** * Package Search returns a list of all matching packages, this will likely * include multiple packages with the same name but different version, release, * epoch. WebUI only wants a list of unique package names, so we need * to strip the duplicate names while preserving order. * * @param pkgs packages returned from search that should be cleaned * @return new list object with duplicates removed; does not change the list in place */ private List<PackageOverview> removeDuplicateNames(List<PackageOverview> pkgs) { List<PackageOverview> result = new ArrayList<PackageOverview>(); Set<String> addedNames = new HashSet<String>(); for (PackageOverview pkgOver : pkgs) { if (!addedNames.contains(pkgOver.getPackageName())) { addedNames.add(pkgOver.getPackageName()); result.add(pkgOver); } } return result; } /** * Make sure we have appropriate defaults no matter how we got here * Set the defaults (where needed) back into the form so that the rest of the action * can find them * @param form where we expect values to end up */ protected void insureFormDefaults(HttpServletRequest request, DynaActionForm form) { String searchCriteria = form.getString(WHERE_CRITERIA); // Default to relevant channels if no search criteria was specified if (searchCriteria == null || searchCriteria.equals("")) { form.set(WHERE_CRITERIA, RELEVANT); } String viewmode = form.getString("view_mode"); if (viewmode.equals("")) { //first time viewing page form.set(VIEW_MODE, OPT_NAME_AND_SUMMARY); } Boolean fineGrained = (Boolean) form.get(FINE_GRAINED); if (fineGrained == null) { fineGrained = false; } if (OPT_FREE_FORM.equals(viewmode)) { // adding a boolean of true to signify we want the results to be // constrained to closer matches, this will force the Lucene Queries // to use a "MUST" instead of the default "SHOULD". It will not // allow fuzzy matches as in spelling errors, but it will allow // free form searches to do more advanced options fineGrained = true; } form.set(FINE_GRAINED, fineGrained); } /** * Build the channel-arch-pulldown for all arches that are not in the 'excluded' list * @return For each arch, a Map of localized display-name and value */ private List<Map<String, String>> buildChannelArches() { List<Map<String, String>> channelArches = new ArrayList<Map<String, String>>(); List<ChannelArch> arches = ChannelManager.getChannelArchitectures(); List<String> syncdLabels = ChannelManager.getSyncdChannelArches(); for (ChannelArch arch : arches) { if (!EXCLUDED_ARCHES.contains(arch.getLabel())) { // if the label does *NOT* exist, this channel arch has no // channels in the database. So we want to flag it. addOption(channelArches, arch.getName(), arch.getLabel(), !syncdLabels.contains(arch.getLabel())); } } return channelArches; } /** * Builds the package-search-option pulldown * @return For each available option, a Map of localized display-name and value */ private List<Map<String, String>> buildSearchOptions() { List<Map<String, String>> searchOptions = new ArrayList<Map<String, String>>(); // setup the option list for select box (view_mode). addOption(searchOptions, "packages.search.free_form", OPT_FREE_FORM); addOption(searchOptions, "packages.search.name", OPT_NAME_ONLY); addOption(searchOptions, "packages.search.name_and_desc", OPT_NAME_AND_DESC); addOption(searchOptions, "packages.search.both", OPT_NAME_AND_SUMMARY); return searchOptions; } }