/**
* Copyright (c) 2013--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;
import java.net.MalformedURLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.log4j.Logger;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.action.DynaActionForm;
import redstone.xmlrpc.XmlRpcException;
import redstone.xmlrpc.XmlRpcFault;
import com.redhat.rhn.common.localization.LocalizationService;
import com.redhat.rhn.common.validator.ValidatorException;
import com.redhat.rhn.domain.rhnpackage.MissingArchitectureException;
import com.redhat.rhn.frontend.struts.RhnAction;
import com.redhat.rhn.frontend.struts.RhnHelper;
import com.redhat.rhn.frontend.taglibs.list.ListTagHelper;
/**
* Base action for searches - this is a place to put the 'magic strings' that connect the
* JSP forms and pages to the action, and to impose a certain amount of uniformity on
* our (currently-very-non-uniform) search processing.
*
* @author ggainey
*
*/
public abstract class BaseSearchAction extends RhnAction {
protected static final Logger LOG = Logger.getLogger(BaseSearchAction.class);
/** Channel-arches a default package-search should look in */
public static final String[] DEFAULT_ARCHES = { "channel-ia32", "channel-ia64",
"channel-x86_64", "channel-s390", "channel-s390x", "channel-ppc" };
/** List of channel arches we don't really support any more. */
public static final List<String> EXCLUDED_ARCHES = Arrays.asList(new String[]
{"channel-sparc", "channel-alpha", "channel-iSeries", "channel-pSeries"});
// combinedSearchForm common keys
public static final String FINE_GRAINED = "fineGrained";
public static final String SEARCH_STR = "search_string";
public static final String VIEW_MODE = "view_mode";
public static final String SEARCH_OPT = "searchOptions";
// Package-specific keys
public static final String ALL_CHANNELS = "allChannels";
public static final String ARCHITECTURE = "architecture";
public static final String CHANNEL = "channel";
public static final String CHANNEL_ARCH = "channel_arch";
public static final String CHANNEL_ARCHES = "channelArches";
public static final String CHANNEL_FILTER = "channel_filter";
public static final String OPT_FREE_FORM = "search_free_form";
public static final String OPT_NAME_AND_DESC = "search_name_and_description";
public static final String OPT_NAME_AND_SUMMARY = "search_name_and_summary";
public static final String OPT_NAME_ONLY = "search_name";
public static final String RELEVANT = "relevant";
public static final String WHERE_CRITERIA = "whereCriteria";
// Errata-specific keys
public static final String OPT_ISSUE_DATE = "optionIssueDateSearch";
public static final String OPT_ADVISORY = "errata_search_by_advisory";
public static final String OPT_PKG_NAME = "errata_search_by_package_name";
public static final String OPT_CVE = "errata_search_by_cve";
public static final String OPT_ALL_FIELDS = "errata_search_by_all_fields";
public static final String ERRATA_BUG = "errata_type_bug";
public static final String ERRATA_SEC = "errata_type_security";
public static final String ERRATA_ENH = "errata_type_enhancement";
// Doc-specific keys
public static final String OPT_CONTENT_ONLY = "search_content";
public static final String OPT_TITLE_ONLY = "search_title";
public static final String OPT_CONTENT_TITLE = "search_content_title";
// System-specific keys
public static final String OPT_GROUPS_MAP = "optGroupsMap";
public static final String OPT_GROUPS_KEYS = "optGroupsKeys";
public static final String WHERE_TO_SEARCH = "whereToSearch";
public static final String INVERT_RESULTS = "invert";
public static final String WHERE_ALL = "all";
public static final String WHERE_SSM = "system_list";
//Xcddf-specific keys
public static final String SCAN_DATE_SEARCH = "optionScanDateSearch";
// addOption keys
public static final String DISPLAY_KEY = "display";
public static final String VALUE_KEY = "value";
/**
* {@inheritDoc}
* The default execute() workflow for search-related actions is to call executeBody(),
* handle any execptions thrown, and return whatever destination executeBody returned.
*/
public ActionForward execute(ActionMapping mapping,
ActionForm formIn,
HttpServletRequest request,
HttpServletResponse response) {
ActionErrors errors = new ActionErrors();
DynaActionForm form = (DynaActionForm)formIn;
request.setAttribute(ListTagHelper.PARENT_URL, request.getRequestURI());
String searchString = form.getString(BaseSearchAction.SEARCH_STR);
ActionForward destination = mapping.findForward(RhnHelper.DEFAULT_FORWARD);
try {
// handle setup, the submission setups the searchstring below
// and redirects to this page which then performs the search.
destination = executeBody(request, mapping, form);
}
catch (XmlRpcException xre) {
LOG.error("Could not connect to search server.", xre);
errors.add(ActionMessages.GLOBAL_MESSAGE,
new ActionMessage("packages.search.connection_error"));
}
catch (XmlRpcFault e) {
LOG.info("Caught Exception :" + e + ", code [" + e.getErrorCode() + "]");
if (e.getErrorCode() == 100) {
LOG.error("Invalid search query", e);
errors.add(ActionMessages.GLOBAL_MESSAGE,
new ActionMessage("packages.search.could_not_parse_query",
searchString));
}
else if (e.getErrorCode() == 200) {
LOG.error("Index files appear to be missing: ", e);
errors.add(ActionMessages.GLOBAL_MESSAGE,
new ActionMessage("packages.search.index_files_missing",
searchString));
}
else {
errors.add(ActionMessages.GLOBAL_MESSAGE,
new ActionMessage("packages.search.could_not_execute_query",
searchString));
}
}
catch (MalformedURLException e) {
LOG.error("Could not connect to server.", e);
errors.add(ActionMessages.GLOBAL_MESSAGE,
new ActionMessage("packages.search.connection_error"));
}
catch (ValidatorException ve) {
errors.add(ActionMessages.GLOBAL_MESSAGE,
new ActionMessage("packages.search.use_free_form"));
}
catch (MissingArchitectureException mae) {
errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
"packages.search.need_one_arch"));
}
if (!errors.isEmpty()) {
addErrors(request, errors);
}
return destination;
}
/**
* This method invokes insureFormDefaults(), followed by doExecute(). Child classes
* are expected to do "reasonable" things in those methods.
* @param request http request
* @param mapping action mapping
* @param form associated form
* @return expected destination from doExecute
* @throws MalformedURLException
* @throws XmlRpcFault
*/
protected ActionForward executeBody(HttpServletRequest request, ActionMapping mapping,
DynaActionForm form)
throws MalformedURLException, XmlRpcFault {
insureFormDefaults(request, form);
return doExecute(request, mapping, form);
}
/**
* This is the guts of a search action - do what needs doing,
* and return what you think the next page should be
* @param request incoming HTTP request
* @param mapping incoming action-mapping
* @param form form associated with this mapping
* @return the desired desitination based on your processing
* @throws MalformedURLException
* @throws XmlRpcFault
*/
protected abstract ActionForward doExecute(HttpServletRequest request,
ActionMapping mapping,
DynaActionForm form)
throws MalformedURLException, XmlRpcFault;
/**
* This gives the child-actions a chance to set up sane defaults no matter how
* the happen to be invoked. Set up the form here, so that it can be relied on
* by code later in the workflow.
*
* @param request incoming HTTP request
* @param form form associated with the request
*/
protected abstract void insureFormDefaults(HttpServletRequest request,
DynaActionForm form);
/**
* Utility function to create options for the dropdown.
* @param options list containing all options.
* @param key resource bundle key used as the display value.
* @param value value to be submitted with form.
*/
protected void addOption(List<Map<String, String>> options, String key, String value) {
addOption(options, key, value, false);
}
/**
* Utility function to create options for the dropdown.
* @param options list containing all options.
* @param key resource bundle key used as the display value.
* @param value value to be submitted with form.
* @param flag Flag the item with an asterisk (*) indicating it is *not*
* synch'd
*/
public void addOption(List<Map<String, String>> options, String key, String value,
boolean flag) {
LocalizationService ls = LocalizationService.getInstance();
Map<String, String> selection = new HashMap<String, String>();
selection.put("display", (flag ? "*" : "") + ls.getMessage(key));
selection.put("value", StringEscapeUtils.escapeHtml(value));
options.add(selection);
}
}