/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/search/trunk/search-tool/tool/src/java/org/sakaiproject/search/tool/SearchBeanImpl.java $
* $Id: SearchBeanImpl.java 110962 2012-08-01 14:46:36Z zqian@umich.edu $
***********************************************************************************
*
* Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009 The Sakai Foundation
*
* Licensed under the Educational Community 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.opensource.org/licenses/ECL-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.sakaiproject.search.tool;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.Collator;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.authz.api.SecurityService;
import org.sakaiproject.component.api.ServerConfigurationService;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.search.api.SearchList;
import org.sakaiproject.search.api.SearchResult;
import org.sakaiproject.search.api.SearchService;
import org.sakaiproject.search.api.TermFrequency;
import org.sakaiproject.search.tool.api.SearchBean;
import org.sakaiproject.search.tool.model.SearchOutputItem;
import org.sakaiproject.search.tool.model.SearchPage;
import org.sakaiproject.search.tool.model.SearchTerm;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.api.SiteService;
import org.sakaiproject.tool.api.Placement;
import org.sakaiproject.tool.api.ToolManager;
import org.sakaiproject.user.api.User;
import org.sakaiproject.user.api.UserDirectoryService;
import org.sakaiproject.util.FormattedText;
import org.sakaiproject.util.StringUtil;
/**
* Implementation of the search bean backing bean
*
* @author ieb
*/
public class SearchBeanImpl implements SearchBean
{
public static final String SEARCH_SITE_IDS = "search_site_ids";
private static final Log log = LogFactory.getLog(SearchBeanImpl.class);
/**
* The searhc string parameter name
*/
private static final String SEARCH_PARAM = "search";
/**
* Where we should be searching (current site, all sites I'm a member of)
*/
private static final String SEARCH_SCOPE = "scope";
public static enum Scope {SITE, MINE};
/**
* The results page
*/
private static final String SEARCH_PAGE = "page";
public static final String SEARCH_ALL_SITES = "search_all_sites";
/**
* The search criteria
*/
private String search;
//private Scope scope = Scope.SITE;
private Scope scope = null;
/**
* The Search Service to use
*/
private SearchService searchService;
private SiteService siteService;
/**
* Time taken
*/
private double timeTaken = 0;
/**
* The number of results per page
*/
private int pagesize = 10;
/**
* The number of list links
*/
private int nlistPages = 5;
/**
* The default request page
*/
private int requestPage = 0;
private int censoredResults = 0;
/**
* The current search list
*/
private SearchList searchResults;
private String placementId;
private String toolId;
private ToolManager toolManager;
private SecurityService securityService;
private ServerConfigurationService serverConfigurationService;
private String siteId;
private String sortName = "normal";
private String filterName = "normal";
private String errorMessage;
private List<TermFrequency> termsVectors;
private List<TermHolder> termList;
private Site currentSite;
private int nTerms = 100;
private List<SearchTerm> finalTermList;
private int topTerms = 10;
private boolean relativeTerms = true;
private float divisorTerms = 3;
private String requestURL;
private String currentUser;
private List<SearchOutputItem> outputItems;
private String searchTermSuggestion = null;
private boolean inPDAPortal = false;
// Empty constructor to aid in testing.
public SearchBeanImpl(String siteId, SearchService ss, String search,ToolManager tm,
SecurityService securityService, ServerConfigurationService serverConfigurationService) {
super();
this.siteId = siteId;
this.searchService = ss;
this.search = search;
this.toolManager = tm;
this.serverConfigurationService = serverConfigurationService;
this.securityService = securityService;
}
/**
* Creates a searchBean
*
* @param request
* The HTTP request
* @param searchService
* The search service to use
* @param siteService
* the site service
* @param portalService
* the portal service
* @throws IdUnusedException
* if there is no current worksite
*/
public SearchBeanImpl(HttpServletRequest request, SearchService searchService,
SiteService siteService, ToolManager toolManager, UserDirectoryService userDirectoryService, SecurityService securityService, ServerConfigurationService serverConfigurationService) throws IdUnusedException
{
this.search = request.getParameter(SEARCH_PARAM);
this.searchService = searchService;
this.siteService = siteService;
this.toolManager = toolManager;
this.placementId = this.toolManager.getCurrentPlacement().getId();
this.toolId = this.toolManager.getCurrentTool().getId();
this.siteId = this.toolManager.getCurrentPlacement().getContext();
this.serverConfigurationService = serverConfigurationService;
this.securityService = securityService;
try
{
this.requestPage = Integer.parseInt(request.getParameter(SEARCH_PAGE));
}
catch (Exception ex)
{
log.debug(ex);
}
currentSite = this.siteService.getSite(this.siteId);
try
{
this.scope = Scope.valueOf(request.getParameter(SEARCH_SCOPE));
}
catch (NullPointerException npe)
{}
catch (IllegalArgumentException iae)
{
log.debug(iae);
log.warn("Invalid Scope Supplied: "+ request.getParameter(SEARCH_SCOPE));
}
User user = userDirectoryService.getCurrentUser();
if (user != null)
currentUser = user.getId();
requestURL = request.getRequestURL().toString();
// normal or pda portal?
checkPdaPortal(request);
}
/**
* check whether pda portal is being used now?
*/
private void checkPdaPortal(HttpServletRequest req)
{
// recognize what to do from the path
String option = req.getContextPath();
String[] parts = {};
if (option != null && !"/".equals(option))
{
//get the parts (the first will be "")
parts = option.split("/");
}
if ((parts.length >= 2) && (parts[2].equals("pda")))
{
inPDAPortal = true;
}
}
/**
* @see #SearchBeanImpl(HttpServletRequest, SearchService, SiteService, ToolManager)
*/
public SearchBeanImpl(HttpServletRequest request, String sortName,
String filterName, SearchService searchService,
SiteService siteService, ToolManager toolManager,
UserDirectoryService userDirectoryService, SecurityService securityService, ServerConfigurationService serverConfigurationService) throws IdUnusedException
{
this(request, searchService, siteService, toolManager, userDirectoryService, securityService, serverConfigurationService);
this.sortName = sortName;
this.filterName = filterName;
}
private void loadTermVectors()
{
List<SearchResult> searchResults = search();
if (searchResults != null)
{
termsVectors = new ArrayList<TermFrequency>();
termList = null;
for (Iterator<SearchResult> i = searchResults.iterator(); i.hasNext();)
{
SearchResult sr = (SearchResult) i.next();
try
{
TermFrequency tf = sr.getTerms();
if (tf != null)
{
termsVectors.add(sr.getTerms());
}
}
catch (IOException e)
{
log.warn("Failed to get term vector ", e);
}
}
}
}
/**
* @deprecated
*/
public String getTerms(String format)
{
List<SearchTerm> l = getTerms();
StringBuilder sb = new StringBuilder();
for (Iterator li = l.iterator(); li.hasNext();)
{
SearchTerm t = (SearchTerm) li.next();
sb.append(MessageFormat.format(format, new Object[] { t.getName(),
t.getWeight() }));
}
return sb.toString();
}
protected class TermHolder
{
public int position;
protected String term;
protected int frequency;
}
private void mergeTerms()
{
if (termsVectors == null)
{
loadTermVectors();
}
if (termsVectors == null)
{
return;
}
HashMap<String, TermHolder> hm = new HashMap<String, TermHolder>();
for (Iterator i = termsVectors.iterator(); i.hasNext();)
{
TermFrequency tf = (TermFrequency) i.next();
String[] terms = tf.getTerms();
int[] freq = tf.getFrequencies();
for (int ti = 0; ti < terms.length; ti++)
{
TermHolder h = (TermHolder) hm.get(terms[ti]);
if (h == null)
{
h = new TermHolder();
h.term = terms[ti];
h.frequency = freq[ti];
hm.put(terms[ti], h);
}
else
{
h.frequency += freq[ti];
}
}
}
termList = new ArrayList<TermHolder>();
termList.addAll(hm.values());
Collections.sort(termList, new Comparator<TermHolder>()
{
public int compare(TermHolder a, TermHolder b)
{
return b.frequency - a.frequency;
}
});
}
public boolean isEnabled()
{
return ("true".equals(serverConfigurationService.getString("search.enable",
"false")));
}
public boolean isScope(String scope)
{
if (this.scope == null)
this.scope = getDefaultSearchScope();
return this.scope.equals(Scope.valueOf(scope));
}
/**
* Gets the current search request
*
* @return current search request
*/
public String getSearch()
{
if (search == null) return "";
return FormattedText.escapeHtml(search, false);
}
/**
* The time taken to perform the search only, not including rendering
*
* @return
*/
public String getTimeTaken()
{
int tt = (int) timeTaken;
return String.valueOf(tt);
}
/* assemble the list of search sites */
protected List<String> getSearchSites (String[] toolPropertySiteIds) {
List<String> l = new ArrayList<String>();
l.add(this.siteId);
if (toolPropertySiteIds != null) {
String[] searchSiteIds = toolPropertySiteIds;
for(int i = 0;i<searchSiteIds.length;i++){
String ss = searchSiteIds[i];
if (searchSiteIds[i].length() > 0) l.add(ss);
}
}
if (scope != null && scope.equals(Scope.MINE)) {
l.addAll(Arrays.asList(getAllUsersSites()));
}
return l;
}
protected String[] getToolPropertySiteIds() {
Properties props = extractPropertiesFromTool();
String[] searchSiteIds;
searchSiteIds = extractSiteIdsFromProperties(props);
return searchSiteIds;
}
/**
* Get all the sites a user has access to.
* @return An array of site IDs.
*/
protected String[] getAllUsersSites() {
List<Site> sites = siteService.getSites(
org.sakaiproject.site.api.SiteService.SelectionType.ACCESS,
null, null, null, null, null);
List<String> siteIds = new ArrayList<String>(sites.size());
for (Site site: sites) {
if (site != null && site.getId() != null) {
siteIds.add(site.getId());
}
}
siteIds.add(siteService.getUserSiteId(currentUser));
return siteIds.toArray(new String[siteIds.size()]);
}
/* get any site ids that are in the tool property and normalize the string.
*
*/
protected String[] extractSiteIdsFromProperties(Properties props) {
// Properties props = extractPropertiesFromTool();
String targetSiteId = StringUtil.trimToNull(props.getProperty(SEARCH_SITE_IDS));
if (targetSiteId == null) return new String[] {""};
String[] searchSiteIds = StringUtil.split(targetSiteId, ",");
for(int i = 0;i<searchSiteIds.length;i++){
searchSiteIds[i] = StringUtil.trimToZero(searchSiteIds[i]);
}
return searchSiteIds;
}
protected Properties extractPropertiesFromTool() {
Placement placement = toolManager.getCurrentPlacement();
Properties props = placement.getPlacementConfig();
if(props.isEmpty())
props = placement.getConfig();
return props;
}
/**
* Perform the search
*
* @return a list of page names that match the search criteria
*/
public SearchList search()
{
if (searchResults == null && errorMessage == null)
{
if (search != null && search.trim().length() > 0)
{
List<String> l = getSearchSites(getToolPropertySiteIds());
long start = System.currentTimeMillis();
int searchStart = requestPage * pagesize;
int searchEnd = searchStart + pagesize;
try
{
searchResults = searchService.search(search, l, searchStart,
searchEnd, filterName, sortName);
if (searchResults != null && searchResults.size() < 3) {
if ((searchResults.size() > 0 && searchResults.get(0).getScore() < 1)) {
log.debug("closest match: " + searchResults.get(0).getScore());
String sug = searchService.getSearchSuggestion(search);
log.debug("got suggestion: " + sug);
this.searchTermSuggestion = sug;
} else if (searchResults.size() == 0) {
log.debug("No hits getting suggestion");
String sug = searchService.getSearchSuggestion(search);
log.debug("got suggestion: " + sug);
this.searchTermSuggestion = sug;
}
} else if (searchResults == null || searchResults.size() == 0) {
log.debug("No hits getting suggestion");
String sug = searchService.getSearchSuggestion(search);
log.debug("got suggestion: " + sug);
this.searchTermSuggestion = sug;
}
}
catch (Exception ex)
{
ex.printStackTrace();
errorMessage = ex.getMessage();
log.warn("Search Error encoutered, generated by a user action "
+ ex.getClass().getName() + ":" + ex.getMessage());
log.debug("Search Error Traceback ", ex);
}
long end = System.currentTimeMillis();
timeTaken = 0.001 * (end - start);
}
}
return searchResults;
}
/**
* @return Returns the nlistPages.
*/
public int getNlistPages()
{
return nlistPages;
}
/**
* @param nlistPages
* The nlistPages to set.
*/
public void setNlistPages(int nlistPages)
{
this.nlistPages = nlistPages;
}
/**
* @return Returns the pagesize.
*/
public int getPagesize()
{
return pagesize;
}
/**
* @param pagesize
* The pagesize to set.
*/
public void setPagesize(int pagesize)
{
this.pagesize = pagesize;
}
/**
* @return Returns the requestPage.
*/
public int getRequestPage()
{
return requestPage;
}
/**
* @param requestPage
* The requestPage to set.
*/
public void setRequestPage(int requestPage)
{
this.requestPage = requestPage;
}
/**
* The Total number of results
*
* @return
*/
public int getNresults()
{
return searchResults.getFullSize();
}
/**
* {@inheritDoc}
*/
public String getSearchTitle()
{
return Messages.getString("search_title") + " " + getSearch();
}
/**
* {@inheritDoc}
*/
public boolean hasAdmin()
{
boolean superUser = securityService.isSuperUser();
return (superUser)
|| ("true".equals(serverConfigurationService.getString(
"search.allow.maintain.admin", "false")) && siteService
.allowUpdateSite(siteId));
}
/**
* {@inheritDoc}
*/
public String getToolUrl()
{
if (inPDAPortal)
{
return serverConfigurationService.getString("portalPath") + "/pda/" + siteId + "/tool/"
+ placementId;
}
else
{
return serverConfigurationService.getString("portalPath") + "/tool/"
+ placementId;
}
}
public boolean hasResults()
{
SearchList sr = (SearchList) search();
if (sr == null)
{
return false;
}
else
{
return (sr.size() > 0);
}
}
public boolean foundNoResults() {
if ( search == null || search.trim().length() == 0 ) {
return false;
}
return !hasResults();
}
public String getOpenSearchUrl()
{
return serverConfigurationService.getPortalUrl() + "/tool/" + placementId
+ "/opensearch";
}
public String getSherlockIconUrl()
{
return FormattedText.escapeHtml(getBaseUrl() + SherlockSearchBeanImpl.UPDATE_IMAGE,false);
}
public String getSherlockUpdateUrl()
{
return FormattedText.escapeHtml(getBaseUrl() + SherlockSearchBeanImpl.UPDATE_URL,false);
}
public String getBaseUrl()
{
return serverConfigurationService.getPortalUrl() + "/tool/" + placementId;
}
public String getPortalBaseUrl()
{
return serverConfigurationService.getPortalUrl() + "/directtool/" + placementId;
}
public String getSiteTitle()
{
return FormattedText.escapeHtml(currentSite.getTitle(), false);
}
public String getSystemName()
{
return FormattedText.escapeHtml(serverConfigurationService.getString(
"ui.service", "Sakai"), false);
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.search.tool.SearchBean#getPages()
*/
public List<SearchPage> getPages()
{
List<SearchPage> pages = new ArrayList<SearchPage>();
try
{
SearchList sr = (SearchList) search();
if (sr == null) return pages;
int npages = (sr.getFullSize()-1) / pagesize;
int cpage = requestPage - (nlistPages / 2);
if (cpage < 0)
{
cpage = 0;
}
int lastPage = Math.min(cpage + nlistPages, npages);
boolean first = true;
if (cpage == lastPage)
{
return pages;
}
else
{
while (cpage <= lastPage)
{
final String searchURL = "?search="
+ URLEncoder.encode(search, "UTF-8") + "&page="
+ String.valueOf(cpage)+"&scope=" + this.scope;
final String name = String.valueOf(cpage + 1);
String cssInd = "1";
if (first)
{
cssInd = "0";
first = false;
}
else if (cpage == (lastPage))
{
cssInd = "2";
}
pages.add(new SearchPage()
{
public String getName()
{
return FormattedText.escapeHtml(name, false);
}
public String getUrl()
{
return FormattedText.escapeHtml(searchURL, false);
}
});
cpage++;
}
}
}
catch (Exception ex)
{
log.debug(ex);
}
return pages;
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.search.tool.SearchBean#getResults()
*/
@SuppressWarnings("unchecked")
public List<SearchOutputItem> getResults()
{
if (outputItems == null) {
outputItems = new ArrayList<SearchOutputItem>();
SearchList sl = search();
for (Iterator<SearchResult> i = sl.iterator(); i.hasNext();)
{
final SearchResult sr = (SearchResult) i.next();
if (!sr.isCensored()) {
outputItems.add(new SearchOutputItem()
{
public String getSearchResult()
{
try
{
return sr.getSearchResult();
}
catch (Exception ex)
{
return "";
}
}
public String getTitle()
{
try
{
return FormattedText.escapeHtml(sr.getTitle(), false);
}
catch (Exception ex)
{
return "";
}
}
public String getTool()
{
try
{
return FormattedText.escapeHtml(sr.getTool(), false);
}
catch (Exception ex)
{
return "";
}
}
public String getUrl()
{
try
{
return FormattedText.escapeHtml(sr.getUrl(), false);
}
catch (Exception ex)
{
return "";
}
}
private Site site = null;
public String getSiteURL() {
if (site == null)
site = getSite();
return (site != null) ? site.getUrl() : null;
}
public String getSiteTitle() {
if (site == null)
site = getSite();
if (site != null)
return FormattedText.escapeHtml(site.getTitle(), false);
return "";
}
private Site getSite() {
try {
Site s = siteService.getSite(sr.getSiteId());
return s;
} catch (IdUnusedException e) {
log.warn("site: " + sr.getSiteId() + "referenced in search results doesn't exits");
}
return null;
}
public boolean isVisible() {
if (sr.isCensored())
return false;
else
return true;
}
public boolean hasPortalUrl() {
return sr.hasPortalUrl();
}
});
} else {
censoredResults++;
}
}
}
return outputItems;
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.search.tool.SearchBean#getSearchFound()
*/
public String getSearchFound()
{
SearchList sr = (SearchList) search();
if (sr == null) return "";
int total = sr.getFullSize();
int start = 0;
int end = 0;
if (total > 0)
{
start = sr.getStart();
end = Math.min(start + sr.size(), total);
start++;
}
return MessageFormat.format(Messages.getString("jsp_found_line"), new Object[] {
Integer.valueOf(start), Integer.valueOf(end), Integer.valueOf(total),
Double.valueOf(timeTaken) });
}
public String getSearchFoundCensored()
{
if (this.getCensoredResultCount() == 1)
return MessageFormat.format(Messages.getString("jsp_results_no_perm_singular"), new Object[] { censoredResults });
else
return MessageFormat.format(Messages.getString("jsp_results_no_perm_plural"), new Object[] { censoredResults });
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.search.tool.SearchBean#getTerms()
*/
public List<SearchTerm> getTerms()
{
if (termList == null)
{
mergeTerms();
finalTermList = null;
}
if (termList == null)
{
return new ArrayList<SearchTerm>();
}
if (finalTermList != null)
{
return finalTermList;
}
finalTermList = new ArrayList<SearchTerm>();
List<TermHolder> l = termList.subList(0, Math.min(nTerms, termList.size()));
int j = 0;
for (Iterator li = l.iterator(); li.hasNext();)
{
TermHolder t = (TermHolder) li.next();
t.position = j;
j++;
}
Collections.sort(l, new Comparator<TermHolder>()
{
Collator c = Collator.getInstance();
public int compare(TermHolder a, TermHolder b)
{
return c.compare(a.term, b.term);
}
});
int factor = 1;
j = l.size();
for (Iterator li = l.iterator(); li.hasNext();)
{
TermHolder t = (TermHolder) li.next();
factor = Math.max(t.frequency, factor);
}
for (Iterator li = l.iterator(); li.hasNext();)
{
final TermHolder t = (TermHolder) li.next();
float f = (float)(topTerms * t.frequency) / factor;
if (relativeTerms)
{
f = (float)(topTerms * (l.size() - t.position)) / l.size();
}
f = f / divisorTerms;
j--;
final String weight = String.valueOf(f);
final String searchScope = this.scope.name();
finalTermList.add(new SearchTerm()
{
public String getName()
{
return FormattedText.escapeHtml(t.term, false);
}
public String getUrl()
{
try
{
return FormattedText
.escapeHtml("?panel=Main&search=" + URLEncoder.encode(t.term,"UTF-8")+"&scope=" + searchScope, false);
}
catch (UnsupportedEncodingException e)
{
return FormattedText
.escapeHtml("?panel=Main&search=" + URLEncoder.encode(t.term), false);
}
}
public String getWeight()
{
return weight;
}
});
}
return finalTermList;
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.search.tool.SearchBean#hasError()
*/
public boolean hasError()
{
return (errorMessage != null);
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.search.tool.SearchBean#getErrorMessage()
*/
public String getErrorMessage()
{
return FormattedText.escapeHtml(errorMessage, false);
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.search.tool.api.SearchBean#getRssUrl()
*/
public String getRssURL()
{
if (hasResults())
{
try
{
return FormattedText.escapeHtml(getToolUrl() + "/rss20?search="
+ URLEncoder.encode(search, "UTF-8")
+ ((scope != null)?"&scope="+ URLEncoder.encode(scope.toString(), "UTF-8"):""), false);
}
catch (UnsupportedEncodingException e)
{
return FormattedText.escapeHtml(getToolUrl() + "/rss20?search="
+ URLEncoder.encode(search)
+ ((scope != null)?"&scope="+ URLEncoder.encode(scope.toString()):"") , false);
}
}
else
{
return null;
}
}
/* (non-Javadoc)
* @see org.sakaiproject.search.tool.api.SearchBean#getDateNow()
*/
public String getDateNow()
{
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
return format.format(new Date(System.currentTimeMillis()));
}
/* (non-Javadoc)
* @see org.sakaiproject.search.tool.api.SearchBean#getRequestUrl()
*/
public String getRequestUrl()
{
return FormattedText.escapeHtml(requestURL, false);
}
private Boolean searchScopeSite = null;
private Scope getDefaultSearchScope() {
log.debug("setting default scope!");
String siteId = toolManager.getCurrentPlacement().getContext();
if (siteService.isUserSite(siteId)) {
log.debug("got scope of Mine");
return Scope.MINE;
} else {
log.debug("got scope of Site");
return Scope.SITE;
}
}
/* (non-Javadoc)
* @see org.sakaiproject.search.tool.api.SearchBean#getCensoredResultCount()
*/
public int getCensoredResultCount() {
getResults();
return censoredResults;
}
public String getSuggestion() {
return searchTermSuggestion;
}
public boolean hasSuggestion() {
if (searchTermSuggestion != null) {
return true;
}
return false;
}
public String getSuggestionUrl() {
String searchURL;
try {
searchURL = "?search="
+ URLEncoder.encode(searchTermSuggestion, "UTF-8") + "&scope=" + this.scope;
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
return FormattedText.escapeHtml(searchURL, false);
}
/**
* {@inheritDoc}
*/
public boolean isSearchServer()
{
return searchService.isSearchServer();
}
}