org.codelibs.fess.web.IndexAction.java Source code

Java tutorial

Introduction

Here is the source code for org.codelibs.fess.web.IndexAction.java

Source

/*
 * Copyright 2009-2015 the CodeLibs Project and the Others.
 *
 * Licensed under the Apache 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.apache.org/licenses/LICENSE-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.codelibs.fess.web;

import java.awt.Desktop;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.NumberFormat;
import java.time.Clock;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import jp.sf.fess.suggest.Suggester;
import jp.sf.fess.suggest.entity.SpellCheckResponse;
import jp.sf.fess.suggest.entity.SuggestResponse;
import jp.sf.fess.suggest.service.SuggestService;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.codelibs.core.util.DynamicProperties;
import org.codelibs.core.util.StringUtil;
import org.codelibs.fess.Constants;
import org.codelibs.fess.InvalidQueryException;
import org.codelibs.fess.ResultOffsetExceededException;
import org.codelibs.fess.UnsupportedSearchException;
import org.codelibs.fess.db.allcommon.CDef;
import org.codelibs.fess.db.exentity.ClickLog;
import org.codelibs.fess.db.exentity.SearchLog;
import org.codelibs.fess.db.exentity.UserInfo;
import org.codelibs.fess.entity.FieldAnalysisResponse;
import org.codelibs.fess.entity.LoginInfo;
import org.codelibs.fess.helper.CrawlingConfigHelper;
import org.codelibs.fess.helper.DocumentHelper;
import org.codelibs.fess.helper.FieldHelper;
import org.codelibs.fess.helper.HotSearchWordHelper;
import org.codelibs.fess.helper.HotSearchWordHelper.Range;
import org.codelibs.fess.helper.LabelTypeHelper;
import org.codelibs.fess.helper.OpenSearchHelper;
import org.codelibs.fess.helper.QueryHelper;
import org.codelibs.fess.helper.RoleQueryHelper;
import org.codelibs.fess.helper.SearchLogHelper;
import org.codelibs.fess.helper.SystemHelper;
import org.codelibs.fess.helper.UserInfoHelper;
import org.codelibs.fess.helper.ViewHelper;
import org.codelibs.fess.screenshot.ScreenShotManager;
import org.codelibs.fess.service.FavoriteLogService;
import org.codelibs.fess.service.SearchService;
import org.codelibs.fess.util.ComponentUtil;
import org.codelibs.fess.util.FacetResponse;
import org.codelibs.fess.util.MoreLikeThisResponse;
import org.codelibs.fess.util.QueryResponseList;
import org.codelibs.fess.util.WebApiUtil;
import org.codelibs.robot.util.CharUtil;
import org.codelibs.sastruts.core.SSCConstants;
import org.codelibs.sastruts.core.exception.SSCActionMessagesException;
import org.codelibs.solr.lib.exception.SolrLibQueryException;
import org.dbflute.optional.OptionalEntity;
import org.seasar.framework.beans.util.Beans;
import org.seasar.framework.container.annotation.tiger.Binding;
import org.seasar.framework.container.annotation.tiger.BindingType;
import org.seasar.framework.container.annotation.tiger.InitMethod;
import org.seasar.framework.util.InputStreamUtil;
import org.seasar.framework.util.OutputStreamUtil;
import org.seasar.framework.util.URLUtil;
import org.seasar.struts.annotation.ActionForm;
import org.seasar.struts.annotation.Execute;
import org.seasar.struts.taglib.S2Functions;
import org.seasar.struts.util.MessageResourcesUtil;
import org.seasar.struts.util.RequestUtil;
import org.seasar.struts.util.ResponseUtil;
import org.seasar.struts.util.URLEncoderUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class IndexAction {

    private static final Logger logger = LoggerFactory.getLogger(IndexAction.class);

    private static final String REDIRECT_TO_INDEX = "index?redirect=true";

    private static final String LABEL_FIELD = "label";

    protected static final long DEFAULT_START_COUNT = 0;

    protected static final int DEFAULT_PAGE_SIZE = 20;

    protected static final int MAX_PAGE_SIZE = 100;

    protected static final int DEFAULT_SUGGEST_PAGE_SIZE = 5;

    protected static final int DEFAULT_SPELLCHECK_PAGE_SIZE = 5;

    protected static final Pattern FIELD_EXTRACTION_PATTERN = Pattern.compile("^([a-zA-Z0-9_]+):.*");

    @ActionForm
    @Resource
    protected IndexForm indexForm;

    @Resource
    protected SearchService searchService;

    @Resource
    protected FavoriteLogService favoriteLogService;

    @Binding(bindingType = BindingType.MAY)
    @Resource
    protected ScreenShotManager screenShotManager;

    @Resource
    protected LabelTypeHelper labelTypeHelper;

    @Resource
    protected ViewHelper viewHelper;

    @Resource
    protected QueryHelper queryHelper;

    @Resource
    protected RoleQueryHelper roleQueryHelper;

    @Resource
    protected UserInfoHelper userInfoHelper;

    @Resource
    protected OpenSearchHelper openSearchHelper;

    @Resource
    protected SystemHelper systemHelper;

    @Resource
    protected FieldHelper fieldHelper;

    @Resource
    protected Suggester suggester;

    @Resource
    protected SuggestService suggestService;

    @Resource
    protected DynamicProperties crawlerProperties;

    @Resource
    protected HttpServletRequest request;

    @Resource
    protected HttpServletResponse response;

    public List<Map<String, Object>> documentItems;

    public FacetResponse facetResponse;

    public MoreLikeThisResponse moreLikeThisResponse;

    public String pageSize;

    public String currentPageNumber;

    public String allRecordCount;

    public String allPageCount;

    public boolean existNextPage;

    public boolean existPrevPage;

    public String currentStartRecordNumber;

    public String currentEndRecordNumber;

    public List<String> pageNumberList;

    public String execTime;

    public String queryTime = Constants.INVALID_NUMERIC_PARAMETER;

    public String searchTime = Constants.INVALID_NUMERIC_PARAMETER;

    public boolean partialResults;

    public List<Map<String, String>> labelTypeItems;

    public List<Map<String, String>> langItems;

    public String errorMessage;

    protected String pagingQuery = null;

    public boolean searchLogSupport;

    public boolean favoriteSupport;

    public boolean screenShotSupport;

    public String username;

    public String appendHighlightQueries;

    @InitMethod
    public void init() {
        searchLogSupport = Constants.TRUE
                .equals(crawlerProperties.getProperty(Constants.SEARCH_LOG_PROPERTY, Constants.TRUE));
        favoriteSupport = Constants.TRUE
                .equals(crawlerProperties.getProperty(Constants.USER_FAVORITE_PROPERTY, Constants.FALSE));
    }

    public String getPagingQuery() {
        if (pagingQuery == null) {
            final StringBuilder buf = new StringBuilder(200);
            if (indexForm.additional != null) {
                final Set<String> fieldSet = new HashSet<String>();
                for (final String additional : indexForm.additional) {
                    if (StringUtil.isNotBlank(additional) && additional.length() < 1000
                            && !hasFieldInQuery(fieldSet, additional)) {
                        buf.append("&additional=").append(S2Functions.u(additional));
                    }
                }
            }
            if (StringUtil.isNotBlank(indexForm.sort)) {
                buf.append("&sort=").append(S2Functions.u(indexForm.sort));
            }
            if (StringUtil.isNotBlank(indexForm.op)) {
                buf.append("&op=").append(S2Functions.u(indexForm.op));
            }
            if (indexForm.lang != null) {
                final Set<String> langSet = new HashSet<String>();
                for (final String lang : indexForm.lang) {
                    if (StringUtil.isNotBlank(lang) && lang.length() < 1000) {
                        if (Constants.ALL_LANGUAGES.equals(lang)) {
                            langSet.clear();
                            break;
                        }
                        final String normalizeLang = systemHelper.normalizeLang(lang);
                        if (normalizeLang != null) {
                            langSet.add(normalizeLang);
                        }
                    }
                }
                if (!langSet.isEmpty()) {
                    for (final String lang : langSet) {
                        buf.append("&lang=").append(S2Functions.u(lang));
                    }
                }
            }
            if (!indexForm.fields.isEmpty()) {
                for (final Map.Entry<String, String[]> entry : indexForm.fields.entrySet()) {
                    final String[] values = entry.getValue();
                    if (values != null) {
                        for (final String v : values) {
                            if (StringUtil.isNotBlank(v)) {
                                buf.append("&fields.").append(S2Functions.u(entry.getKey())).append('=')
                                        .append(S2Functions.u(v));
                            }
                        }
                    }
                }
            }

            pagingQuery = buf.toString();
        }
        return pagingQuery;
    }

    @Execute(validator = false, input = "index.jsp")
    public String index() {
        searchAvailable();

        buildViewParams();
        buildInitParams();

        return "index.jsp";
    }

    protected void searchAvailable() {
        final String supportedSearch = crawlerProperties.getProperty(Constants.SUPPORTED_SEARCH_FEATURE_PROPERTY,
                Constants.SUPPORTED_SEARCH_WEB);
        if (Constants.SUPPORTED_SEARCH_NONE.equals(supportedSearch)) {
            throw new UnsupportedSearchException(
                    "A search is not supported: " + RequestUtil.getRequest().getRequestURL());
        }
    }

    protected String doSearch() {
        searchAvailable();

        if (StringUtil.isBlank(indexForm.query)) {
            try {
                final String optionQuery = queryHelper.buildOptionQuery(indexForm.options);
                indexForm.query = optionQuery;
            } catch (final InvalidQueryException e) {
                if (logger.isDebugEnabled()) {
                    logger.debug(e.getMessage(), e);
                }
                throw new SSCActionMessagesException(e, e.getMessageCode());
            }
        }

        if (StringUtil.isBlank(indexForm.query) && indexForm.fields.isEmpty()) {
            // redirect to index page
            indexForm.query = null;
            return REDIRECT_TO_INDEX;
        }

        updateSearchParams();
        buildViewParams();
        doSearchInternal();

        return "search.jsp";
    }

    @Execute(validator = true, input = "index")
    public String cache() {
        Map<String, Object> doc = null;
        try {
            doc = searchService.getDocument(fieldHelper.docIdField + ":" + indexForm.docId,
                    queryHelper.getCacheResponseFields(), null);
        } catch (final Exception e) {
            logger.warn("Failed to request: " + indexForm.docId, e);
        }
        if (doc == null) {
            errorMessage = MessageResourcesUtil.getMessage(RequestUtil.getRequest().getLocale(),
                    "errors.docid_not_found", indexForm.docId);
            return "error.jsp";
        }

        final String content = viewHelper.createCacheContent(doc, indexForm.hq);
        if (content == null) {
            errorMessage = MessageResourcesUtil.getMessage(RequestUtil.getRequest().getLocale(),
                    "errors.docid_not_found", indexForm.docId);
            return "error.jsp";
        }
        ResponseUtil.write(content, "text/html", Constants.UTF_8);

        return null;
    }

    @Execute(validator = true, input = "index")
    public String go() throws IOException {
        Map<String, Object> doc = null;
        try {
            doc = searchService.getDocument(fieldHelper.docIdField + ":" + indexForm.docId,
                    queryHelper.getResponseFields(), new String[] { fieldHelper.clickCountField });
        } catch (final Exception e) {
            logger.warn("Failed to request: " + indexForm.docId, e);
        }
        if (doc == null) {
            errorMessage = MessageResourcesUtil.getMessage(RequestUtil.getRequest().getLocale(),
                    "errors.docid_not_found", indexForm.docId);
            return "error.jsp";
        }
        final Object urlObj = doc.get(fieldHelper.urlField);
        if (urlObj == null) {
            errorMessage = MessageResourcesUtil.getMessage(RequestUtil.getRequest().getLocale(),
                    "errors.document_not_found", indexForm.docId);
            return "error.jsp";
        }
        final String url = urlObj.toString();

        if (Constants.TRUE.equals(crawlerProperties.getProperty(Constants.SEARCH_LOG_PROPERTY, Constants.TRUE))) {
            final String userSessionId = userInfoHelper.getUserCode();
            if (userSessionId != null) {
                final SearchLogHelper searchLogHelper = ComponentUtil.getSearchLogHelper();
                final ClickLog clickLog = new ClickLog();
                clickLog.setUrl(url);
                LocalDateTime now = systemHelper.getCurrentTime();
                clickLog.setRequestedTime(now);
                clickLog.setQueryRequestedTime(LocalDateTime
                        .ofInstant(Instant.ofEpochMilli(Long.parseLong(indexForm.rt)), ZoneId.systemDefault()));
                clickLog.setUserSessionId(userSessionId);
                clickLog.setDocId(indexForm.docId);
                long clickCount = 0;
                final Object count = doc.get(fieldHelper.clickCountField);
                if (count instanceof Long) {
                    clickCount = ((Long) count).longValue();
                }
                clickLog.setClickCount(clickCount);
                searchLogHelper.addClickLog(clickLog);
            }
        }

        String hash;
        if (StringUtil.isNotBlank(indexForm.hash)) {
            final String value = URLUtil.decode(indexForm.hash, Constants.UTF_8);
            final StringBuilder buf = new StringBuilder(value.length() + 100);
            for (final char c : value.toCharArray()) {
                if (CharUtil.isUrlChar(c) || c == ' ') {
                    buf.append(c);
                } else {
                    try {
                        buf.append(URLEncoder.encode(String.valueOf(c), Constants.UTF_8));
                    } catch (final UnsupportedEncodingException e) {
                        // NOP
                    }
                }
            }
            hash = buf.toString();
        } else {
            hash = StringUtil.EMPTY;
        }

        if (isFileSystemPath(url)) {
            if (Constants.TRUE
                    .equals(crawlerProperties.getProperty(Constants.SEARCH_FILE_PROXY_PROPERTY, Constants.TRUE))) {
                final CrawlingConfigHelper crawlingConfigHelper = ComponentUtil.getCrawlingConfigHelper();
                try {
                    crawlingConfigHelper.writeContent(doc);
                    return null;
                } catch (final Exception e) {
                    logger.error("Failed to load: " + doc, e);
                    errorMessage = MessageResourcesUtil.getMessage(RequestUtil.getRequest().getLocale(),
                            "errors.not_load_from_server", url);
                    return "error.jsp";
                }
            } else if (Constants.TRUE
                    .equals(crawlerProperties.getProperty(Constants.SEARCH_DESKTOP_PROPERTY, Constants.FALSE))) {
                final String path = url.replaceFirst("file:/+", "//");
                final File file = new File(path);
                if (!file.exists()) {
                    errorMessage = MessageResourcesUtil.getMessage(RequestUtil.getRequest().getLocale(),
                            "errors.not_found_on_file_system", url);
                    return "error.jsp";
                }
                final Desktop desktop = Desktop.getDesktop();
                try {
                    desktop.open(file);
                } catch (final Exception e) {
                    errorMessage = MessageResourcesUtil.getMessage(RequestUtil.getRequest().getLocale(),
                            "errors.could_not_open_on_system", url);
                    logger.warn("Could not open " + path, e);
                    return "error.jsp";
                }

                ResponseUtil.getResponse().setStatus(HttpServletResponse.SC_NO_CONTENT);
                return null;
            } else {
                ResponseUtil.getResponse().sendRedirect(url + hash);
            }
        } else {
            ResponseUtil.getResponse().sendRedirect(url + hash);
        }
        return null;
    }

    @Execute(validator = false, input = "index")
    public String search() {
        if (viewHelper.isUseSession() && StringUtil.isNotBlank(indexForm.num)) {
            normalizePageNum();
            final HttpSession session = request.getSession();
            if (session != null) {
                session.setAttribute(Constants.RESULTS_PER_PAGE, indexForm.num);
            }
        }

        return doSearch();
    }

    @Execute(validator = false, input = "index")
    public String prev() {
        return doMove(-1);
    }

    @Execute(validator = false, input = "index")
    public String next() {
        return doMove(1);
    }

    @Execute(validator = false, input = "index")
    public String move() {
        return doMove(0);
    }

    @Execute(validator = false)
    public String screenshot() {
        OutputStream out = null;
        BufferedInputStream in = null;
        try {
            final Map<String, Object> doc = searchService
                    .getDocument(fieldHelper.docIdField + ":" + indexForm.docId);
            final String url = doc == null ? null : (String) doc.get(fieldHelper.urlField);
            if (StringUtil.isBlank(indexForm.queryId) || StringUtil.isBlank(url) || screenShotManager == null) {
                // 404
                response.sendError(HttpServletResponse.SC_NOT_FOUND);
                return null;
            }

            final File screenShotFile = screenShotManager.getScreenShotFile(indexForm.queryId, indexForm.docId);
            if (screenShotFile == null) {
                // 404
                response.sendError(HttpServletResponse.SC_NOT_FOUND);
                screenShotManager.generate(doc);
                return null;
            }

            response.setContentType(getImageMimeType(screenShotFile));

            out = response.getOutputStream();
            in = new BufferedInputStream(new FileInputStream(screenShotFile));
            InputStreamUtil.copy(in, out);
            OutputStreamUtil.flush(out);
        } catch (final Exception e) {
            logger.error("Failed to response: " + indexForm.docId, e);
        } finally {
            IOUtils.closeQuietly(in);
            IOUtils.closeQuietly(out);
        }

        return null;
    }

    @Execute(validator = false)
    public String searchApi() {
        try {
            WebApiUtil.setObject("searchQuery", doSearchInternal());
            WebApiUtil.setObject("searchTime", searchTime);
            WebApiUtil.setObject("queryTime", queryTime);
            WebApiUtil.setObject("execTime", execTime);
            WebApiUtil.setObject("pageSize", pageSize);
            WebApiUtil.setObject("currentPageNumber", currentPageNumber);
            WebApiUtil.setObject("allRecordCount", allRecordCount);
            WebApiUtil.setObject("allPageCount", allPageCount);
            WebApiUtil.setObject("documentItems", documentItems);
            WebApiUtil.setObject("facetResponse", facetResponse);
            WebApiUtil.setObject("moreLikeThisResponse", moreLikeThisResponse);
        } catch (final Exception e) {
            WebApiUtil.setError(1, e);
        }
        return null;
    }

    @Execute(validator = false)
    public String suggestApi() {
        if (Constants.FALSE
                .equals(crawlerProperties.getProperty(Constants.WEB_API_SUGGEST_PROPERTY, Constants.TRUE))) {
            WebApiUtil.setError(9, "Unsupported operation.");
            return null;
        }

        if (indexForm.fn == null || indexForm.fn.length == 0) {
            WebApiUtil.setError(2, "The field name is empty.");
            return null;
        }

        if (StringUtil.isBlank(indexForm.query)) {
            WebApiUtil.setError(3, "Your query is empty.");
            return null;
        }

        if (StringUtil.isBlank(indexForm.num)) {
            indexForm.num = String.valueOf(DEFAULT_SUGGEST_PAGE_SIZE);
        }

        int num = Integer.parseInt(indexForm.num);
        if (num > getMaxPageSize()) {
            num = getMaxPageSize();
        }

        final String[] fieldNames = indexForm.fn;
        final String[] labels = indexForm.fields.get("label");

        final List<SuggestResponse> suggestResultList = new ArrayList<SuggestResponse>();
        WebApiUtil.setObject("suggestResultList", suggestResultList);

        final List<String> suggestFieldName = Arrays.asList(fieldNames);
        WebApiUtil.setObject("suggestFieldName", suggestFieldName);

        final List<String> labelList;
        if (labels == null) {
            labelList = new ArrayList<String>();
        } else {
            labelList = Arrays.asList(labels);
        }

        try {
            final Set<String> roleSet;
            if (roleQueryHelper != null) {
                roleSet = roleQueryHelper.build();
            } else {
                roleSet = new HashSet<>();
            }

            final SuggestResponse suggestResponse = suggestService.getSuggestResponse(indexForm.query,
                    suggestFieldName, labelList, new ArrayList<String>(roleSet), num);

            if (!suggestResponse.isEmpty()) {
                suggestResultList.add(suggestResponse);
            }

            WebApiUtil.setObject("suggestRecordCount", 1);
        } catch (final Exception e) {
            WebApiUtil.setError(1, e);
        }
        return null;
    }

    @Execute(validator = false)
    public String spellCheckApi() {
        if (Constants.FALSE
                .equals(crawlerProperties.getProperty(Constants.WEB_API_SPELLCHECK_PROPERTY, Constants.TRUE))) {
            WebApiUtil.setError(9, "Unsupported operation.");
            return null;
        }

        if (indexForm.fn == null || indexForm.fn.length == 0) {
            WebApiUtil.setError(2, "The field name is empty.");
            return null;
        }

        if (StringUtil.isBlank(indexForm.query)) {
            WebApiUtil.setError(3, "Your query is empty.");
            return null;
        }

        if (StringUtil.isBlank(indexForm.num)) {
            indexForm.num = String.valueOf(DEFAULT_SPELLCHECK_PAGE_SIZE);
        }

        int num = Integer.parseInt(indexForm.num);
        if (num > getMaxPageSize()) {
            num = getMaxPageSize();
        }

        final String[] fieldNames = indexForm.fn;
        final String[] labels = indexForm.fields.get("label");

        final List<SpellCheckResponse> spellCheckResultList = new ArrayList<SpellCheckResponse>();
        WebApiUtil.setObject("spellCheckResultList", spellCheckResultList);

        final List<String> spellCheckFieldName = Arrays.asList(fieldNames);
        WebApiUtil.setObject("spellCheckFieldName", spellCheckFieldName);

        final List<String> labelList;
        if (labels == null) {
            labelList = new ArrayList<String>();
        } else {
            labelList = Arrays.asList(labels);
        }

        try {
            final Set<String> roleSet;
            if (roleQueryHelper != null) {
                roleSet = roleQueryHelper.build();
            } else {
                roleSet = new HashSet<>();
            }

            final SpellCheckResponse spellCheckResponse = suggestService.getSpellCheckResponse(indexForm.query,
                    spellCheckFieldName, labelList, new ArrayList<String>(roleSet), num);

            if (!spellCheckResponse.isEmpty()) {
                spellCheckResultList.add(spellCheckResponse);
            }

            WebApiUtil.setObject("spellCheckRecordCount", 1);
        } catch (final Exception e) {
            WebApiUtil.setError(1, e);
        }
        return null;
    }

    @Execute(validator = false)
    public String analysisApi() {
        if (Constants.FALSE
                .equals(crawlerProperties.getProperty(Constants.WEB_API_ANALYSIS_PROPERTY, Constants.TRUE))) {
            WebApiUtil.setError(9, "Unsupported operation.");
            return null;
        }

        if (indexForm.fn == null || indexForm.fn.length == 0) {
            WebApiUtil.setError(2, "The field name is empty.");
            return null;
        }

        if (StringUtil.isBlank(indexForm.query)) {
            WebApiUtil.setError(3, "Your query is empty.");
            return null;
        }

        try {
            final String[] fieldNames = indexForm.fn;
            final FieldAnalysisResponse fieldAnalysis = searchService.getFieldAnalysisResponse(fieldNames,
                    indexForm.query);
            WebApiUtil.setObject("fieldAnalysis", fieldAnalysis);
        } catch (final Exception e) {
            WebApiUtil.setError(1, e);
        }
        return null;
    }

    @Execute(validator = false)
    public String hotSearchWordApi() {
        if (Constants.FALSE.equals(
                crawlerProperties.getProperty(Constants.WEB_API_HOT_SEARCH_WORD_PROPERTY, Constants.TRUE))) {
            WebApiUtil.setError(9, "Unsupported operation.");
            return null;
        }

        Range range;
        if (indexForm.range == null) {
            range = Range.ENTIRE;
        } else if ("day".equals(indexForm.range) || "1".equals(indexForm.range)) {
            range = Range.ONE_DAY;
        } else if ("week".equals(indexForm.range) || "7".equals(indexForm.range)) {
            range = Range.ONE_DAY;
        } else if ("month".equals(indexForm.range) || "30".equals(indexForm.range)) {
            range = Range.ONE_DAY;
        } else if ("year".equals(indexForm.range) || "365".equals(indexForm.range)) {
            range = Range.ONE_DAY;
        } else {
            range = Range.ENTIRE;
        }

        try {
            final HotSearchWordHelper hotSearchWordHelper = ComponentUtil.getHotSearchWordHelper();
            final List<String> hotSearchWordList = hotSearchWordHelper.getHotSearchWordList(range);
            WebApiUtil.setObject("hotSearchWordList", hotSearchWordList);
        } catch (final Exception e) {
            WebApiUtil.setError(1, e);
        }
        return null;

    }

    @Execute(validator = false)
    public String favoriteApi() {
        if (Constants.FALSE
                .equals(crawlerProperties.getProperty(Constants.USER_FAVORITE_PROPERTY, Constants.FALSE))) {
            WebApiUtil.setError(9, "Unsupported operation.");
            return null;
        }

        try {
            final Map<String, Object> doc = indexForm.docId == null ? null
                    : searchService.getDocument(fieldHelper.docIdField + ":" + indexForm.docId,
                            queryHelper.getResponseFields(), new String[] { fieldHelper.favoriteCountField });
            final String userCode = userInfoHelper.getUserCode();
            final String favoriteUrl = doc == null ? null : (String) doc.get(fieldHelper.urlField);

            if (StringUtil.isBlank(userCode)) {
                WebApiUtil.setError(2, "No user session.");
                return null;
            } else if (StringUtil.isBlank(favoriteUrl)) {
                WebApiUtil.setError(2, "URL is null.");
                return null;
            }

            final String[] docIds = userInfoHelper
                    .getResultDocIds(URLDecoder.decode(indexForm.queryId, Constants.UTF_8));
            if (docIds == null) {
                WebApiUtil.setError(6, "No searched urls.");
                return null;
            }

            boolean found = false;
            for (final String docId : docIds) {
                if (indexForm.docId.equals(docId)) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                WebApiUtil.setError(5, "Not found: " + favoriteUrl);
                return null;
            }

            if (!favoriteLogService.addUrl(userCode, favoriteUrl)) {
                WebApiUtil.setError(4, "Failed to add url: " + favoriteUrl);
                return null;
            }

            final DocumentHelper documentHelper = ComponentUtil.getDocumentHelper();
            final Object count = doc.get(fieldHelper.favoriteCountField);
            if (count instanceof Long) {
                documentHelper.update(indexForm.docId, fieldHelper.favoriteCountField,
                        ((Long) count).longValue() + 1);
            } else {
                WebApiUtil.setError(7, "Failed to update count: " + favoriteUrl);
                return null;
            }
        } catch (final Exception e) {
            WebApiUtil.setError(1, e);
        }
        return null;

    }

    @Execute(validator = false)
    public String favoritesApi() {
        if (Constants.FALSE
                .equals(crawlerProperties.getProperty(Constants.USER_FAVORITE_PROPERTY, Constants.FALSE))) {
            WebApiUtil.setError(9, "Unsupported operation.");
            return null;
        }

        try {
            final String userCode = userInfoHelper.getUserCode();

            if (StringUtil.isBlank(userCode)) {
                WebApiUtil.setError(2, "No user session.");
                return null;
            } else if (StringUtil.isBlank(indexForm.queryId)) {
                WebApiUtil.setError(3, "Query ID is null.");
                return null;
            }

            final String[] docIds = userInfoHelper.getResultDocIds(indexForm.queryId);
            final List<Map<String, Object>> docList = searchService.getDocumentListByDocIds(docIds,
                    queryHelper.getResponseFields(), new String[] { fieldHelper.favoriteCountField },
                    getMaxPageSize());
            List<String> urlList = new ArrayList<String>(docList.size());
            for (final Map<String, Object> doc : docList) {
                final Object urlObj = doc.get(fieldHelper.urlField);
                if (urlObj != null) {
                    urlList.add(urlObj.toString());
                }
            }
            urlList = favoriteLogService.getUrlList(userCode, urlList);
            final List<String> docIdList = new ArrayList<String>(urlList.size());
            for (final Map<String, Object> doc : docList) {
                final Object urlObj = doc.get(fieldHelper.urlField);
                if (urlObj != null && urlList.contains(urlObj.toString())) {
                    final Object docIdObj = doc.get(fieldHelper.docIdField);
                    if (docIdObj != null) {
                        docIdList.add(docIdObj.toString());
                    }
                }
            }

            WebApiUtil.setObject("docIdList", docIdList);
        } catch (final Exception e) {
            WebApiUtil.setError(1, e);
        }
        return null;

    }

    @Execute(validator = false)
    public String osdd() {
        openSearchHelper.write(ResponseUtil.getResponse());
        return null;
    }

    @Execute(validator = false, input = "index")
    public String help() {
        buildViewParams();
        buildInitParams();
        return "help.jsp";
    }

    protected String doSearchInternal() {
        final StringBuilder queryBuf = new StringBuilder(255);
        if (StringUtil.isNotBlank(indexForm.query)) {
            queryBuf.append(indexForm.query);
        }
        if (StringUtil.isNotBlank(indexForm.op)) {
            request.setAttribute(Constants.DEFAULT_OPERATOR, indexForm.op);
        }
        if (queryBuf.indexOf(" OR ") >= 0) {
            queryBuf.insert(0, '(').append(')');
        }
        if (indexForm.additional != null) {
            final Set<String> fieldSet = new HashSet<String>();
            for (final String additional : indexForm.additional) {
                if (StringUtil.isNotBlank(additional) && additional.length() < 1000
                        && !hasFieldInQuery(fieldSet, additional)) {
                    queryBuf.append(' ').append(additional);
                }
            }
        }
        if (!indexForm.fields.isEmpty()) {
            for (final Map.Entry<String, String[]> entry : indexForm.fields.entrySet()) {
                final List<String> valueList = new ArrayList<String>();
                final String[] values = entry.getValue();
                if (values != null) {
                    for (final String v : values) {
                        valueList.add(v);
                    }
                }
                if (valueList.size() == 1) {
                    queryBuf.append(' ').append(entry.getKey()).append(":\"").append(valueList.get(0)).append('\"');
                } else if (valueList.size() > 1) {
                    queryBuf.append(" (");
                    for (int i = 0; i < valueList.size(); i++) {
                        if (i != 0) {
                            queryBuf.append(" OR");
                        }
                        queryBuf.append(' ').append(entry.getKey()).append(":\"").append(valueList.get(i))
                                .append('\"');
                    }
                    queryBuf.append(')');
                }

            }
        }
        if (StringUtil.isNotBlank(indexForm.sort)) {
            queryBuf.append(" sort:").append(indexForm.sort);
        }
        if (indexForm.lang != null) {
            final Set<String> langSet = new HashSet<>();
            for (final String lang : indexForm.lang) {
                if (StringUtil.isNotBlank(lang) && lang.length() < 1000) {
                    if (Constants.ALL_LANGUAGES.equalsIgnoreCase(lang)) {
                        langSet.add(Constants.ALL_LANGUAGES);
                    } else {
                        final String normalizeLang = systemHelper.normalizeLang(lang);
                        if (normalizeLang != null) {
                            langSet.add(normalizeLang);
                        }
                    }
                }
            }
            if (langSet.size() > 1 && langSet.contains(Constants.ALL_LANGUAGES)) {
                langSet.clear();
                indexForm.lang = new String[] { Constants.ALL_LANGUAGES };
            } else {
                langSet.remove(Constants.ALL_LANGUAGES);
            }
            appendLangQuery(queryBuf, langSet);
        } else if (Constants.TRUE.equals(
                crawlerProperties.getProperty(Constants.USE_BROWSER_LOCALE_FOR_SEARCH_PROPERTY, Constants.FALSE))) {
            final Set<String> langSet = new HashSet<>();
            final Enumeration<Locale> locales = request.getLocales();
            if (locales != null) {
                while (locales.hasMoreElements()) {
                    final Locale locale = locales.nextElement();
                    final String normalizeLang = systemHelper.normalizeLang(locale.toString());
                    if (normalizeLang != null) {
                        langSet.add(normalizeLang);
                    }
                }
                if (!langSet.isEmpty()) {
                    appendLangQuery(queryBuf, langSet);
                }
            }
        }

        final String query = queryBuf.toString().trim();

        // init pager
        if (StringUtil.isBlank(indexForm.start)) {
            indexForm.start = String.valueOf(DEFAULT_START_COUNT);
        } else {
            try {
                Long.parseLong(indexForm.start);
            } catch (final NumberFormatException e) {
                indexForm.start = String.valueOf(DEFAULT_START_COUNT);
            }
        }
        if (StringUtil.isBlank(indexForm.num)) {
            indexForm.num = String.valueOf(getDefaultPageSize());
        }
        normalizePageNum();

        final int pageStart = Integer.parseInt(indexForm.start);
        final int pageNum = Integer.parseInt(indexForm.num);
        try {
            documentItems = searchService.getDocumentList(query, pageStart, pageNum, indexForm.facet, indexForm.geo,
                    indexForm.mlt, queryHelper.getResponseFields(), queryHelper.getResponseDocValuesFields());
        } catch (final SolrLibQueryException e) {
            if (logger.isDebugEnabled()) {
                logger.debug(e.getMessage(), e);
            }
            throw new SSCActionMessagesException(e, "errors.invalid_query_unknown");
        } catch (final InvalidQueryException e) {
            if (logger.isDebugEnabled()) {
                logger.debug(e.getMessage(), e);
            }
            throw new SSCActionMessagesException(e, e.getMessageCode());
        } catch (final ResultOffsetExceededException e) {
            if (logger.isDebugEnabled()) {
                logger.debug(e.getMessage(), e);
            }
            throw new SSCActionMessagesException(e, "errors.result_size_exceeded");
        }
        // search
        final QueryResponseList queryResponseList = (QueryResponseList) documentItems;
        facetResponse = queryResponseList.getFacetResponse();
        moreLikeThisResponse = queryResponseList.getMoreLikeThisResponse();
        final NumberFormat nf = NumberFormat.getInstance(RequestUtil.getRequest().getLocale());
        nf.setMaximumIntegerDigits(2);
        nf.setMaximumFractionDigits(2);
        try {
            execTime = nf.format((double) queryResponseList.getExecTime() / 1000);
        } catch (final Exception e) {
            // ignore
        }

        final Clock clock = Clock.systemDefaultZone();
        indexForm.rt = Long.toString(clock.millis());

        // favorite
        if (favoriteSupport || screenShotManager != null) {
            indexForm.queryId = userInfoHelper.generateQueryId(query, documentItems);
            if (screenShotManager != null) {
                screenShotManager.storeRequest(indexForm.queryId, documentItems);
                screenShotSupport = true;
            }
        }

        // search log
        if (searchLogSupport) {
            final LocalDateTime now = systemHelper.getCurrentTime();

            final SearchLogHelper searchLogHelper = ComponentUtil.getSearchLogHelper();
            final SearchLog searchLog = new SearchLog();

            String userCode = null;
            if (Constants.TRUE
                    .equals(crawlerProperties.getProperty(Constants.USER_INFO_PROPERTY, Constants.TRUE))) {
                userCode = userInfoHelper.getUserCode();
                if (StringUtil.isNotBlank(userCode)) {
                    final UserInfo userInfo = new UserInfo();
                    userInfo.setCode(userCode);
                    userInfo.setCreatedTime(now);
                    userInfo.setUpdatedTime(now);
                    searchLog.setUserInfo(OptionalEntity.of(userInfo));
                }
            }

            searchLog.setHitCount(queryResponseList.getAllRecordCount());
            searchLog.setResponseTime(Integer.valueOf((int) queryResponseList.getExecTime()));
            searchLog.setSearchWord(StringUtils.abbreviate(query, 1000));
            searchLog.setSearchQuery(StringUtils.abbreviate(queryResponseList.getSearchQuery(), 1000));
            searchLog.setSolrQuery(StringUtils.abbreviate(queryResponseList.getSolrQuery(), 1000));
            searchLog.setRequestedTime(now);
            searchLog.setQueryOffset(pageStart);
            searchLog.setQueryPageSize(pageNum);

            searchLog.setClientIp(StringUtils.abbreviate(request.getRemoteAddr(), 50));
            searchLog.setReferer(StringUtils.abbreviate(request.getHeader("referer"), 1000));
            searchLog.setUserAgent(StringUtils.abbreviate(request.getHeader("user-agent"), 255));
            if (userCode != null) {
                searchLog.setUserSessionId(userCode);
            }
            final Object accessType = request.getAttribute(Constants.SEARCH_LOG_ACCESS_TYPE);
            if (accessType instanceof CDef.AccessType) {
                switch ((CDef.AccessType) accessType) {
                case Json:
                    searchLog.setAccessType_Json();
                    searchLog.setAccessType_Others();
                    searchLog.setAccessType_Xml();
                    break;
                case Xml:
                    searchLog.setAccessType_Xml();
                    break;
                case Others:
                    searchLog.setAccessType_Others();
                    break;
                default:
                    searchLog.setAccessType_Web();
                    break;
                }
            } else {
                searchLog.setAccessType_Web();
            }

            @SuppressWarnings("unchecked")
            final Map<String, List<String>> fieldLogMap = (Map<String, List<String>>) request
                    .getAttribute(Constants.FIELD_LOGS);
            if (fieldLogMap != null) {
                for (final Map.Entry<String, List<String>> logEntry : fieldLogMap.entrySet()) {
                    for (final String value : logEntry.getValue()) {
                        searchLog.addSearchFieldLogValue(logEntry.getKey(), StringUtils.abbreviate(value, 1000));
                    }
                }
            }

            searchLogHelper.addSearchLog(searchLog);
        }

        final String[] highlightQueries = (String[]) request.getAttribute(Constants.HIGHLIGHT_QUERIES);
        if (highlightQueries != null) {
            final StringBuilder buf = new StringBuilder(100);
            for (final String q : highlightQueries) {
                buf.append("&hq=").append(q);
            }
            appendHighlightQueries = buf.toString();
        }

        Beans.copy(documentItems, this)
                .includes("pageSize", "currentPageNumber", "allRecordCount", "allPageCount", "existNextPage",
                        "existPrevPage", "currentStartRecordNumber", "currentEndRecordNumber", "pageNumberList",
                        "partialResults", "queryTime", "searchTime")
                .execute();

        return query;
    }

    private void appendLangQuery(final StringBuilder queryBuf, final Set<String> langSet) {
        if (langSet.size() == 1) {
            queryBuf.append(' ').append(fieldHelper.langField).append(':').append(langSet.iterator().next());
        } else if (langSet.size() > 1) {
            boolean first = true;
            for (final String lang : langSet) {
                if (first) {
                    queryBuf.append(" (");
                    first = false;
                } else {
                    queryBuf.append(" OR ");
                }
                queryBuf.append(fieldHelper.langField).append(':').append(lang);
            }
            queryBuf.append(')');
        }
    }

    protected void updateSearchParams() {
        if (indexForm.facet == null) {
            indexForm.facet = queryHelper.getDefaultFacetInfo();
        }

        if (indexForm.mlt == null) {
            indexForm.mlt = queryHelper.getDefaultMoreLikeThisInfo();
        }

        if (indexForm.geo == null) {
            indexForm.geo = queryHelper.getDefaultGeoInfo();
        }
    }

    protected boolean isFileSystemPath(final String url) {
        return url.startsWith("file:") || url.startsWith("smb:");
    }

    protected String doMove(final int move) {
        int pageNum = getDefaultPageSize();
        if (StringUtil.isBlank(indexForm.num)) {
            indexForm.num = String.valueOf(getDefaultPageSize());
        } else {
            try {
                pageNum = Integer.parseInt(indexForm.num);
            } catch (final NumberFormatException e) {
                indexForm.num = String.valueOf(getDefaultPageSize());
            }
        }

        if (StringUtil.isBlank(indexForm.pn)) {
            indexForm.start = String.valueOf(DEFAULT_START_COUNT);
        } else {
            Integer pageNumber = Integer.parseInt(indexForm.pn);
            if (pageNumber != null && pageNumber > 0) {
                pageNumber = pageNumber + move;
                if (pageNumber < 1) {
                    pageNumber = 1;
                }
                indexForm.start = String.valueOf((pageNumber - 1) * pageNum);
            } else {
                indexForm.start = String.valueOf(DEFAULT_START_COUNT);
            }
        }

        return doSearch();
    }

    public List<Map<String, String>> getLabelTypeItems() {
        return labelTypeItems;
    }

    public boolean isDisplayLabelTypeItems() {
        if (labelTypeItems != null) {
            return !labelTypeItems.isEmpty();
        } else {
            return false;
        }
    }

    public String getDisplayQuery() {
        final StringBuilder buf = new StringBuilder(100);
        buf.append(indexForm.query);
        if (!indexForm.fields.isEmpty() && indexForm.fields.containsKey(LABEL_FIELD)) {
            final String[] values = indexForm.fields.get(LABEL_FIELD);
            final List<String> labelList = new ArrayList<String>();
            if (values != null) {
                for (final String v : values) {
                    labelList.add(v);
                }
            }
            for (final String labelTypeValue : labelList) {
                for (final Map<String, String> map : labelTypeItems) {
                    if (map.get(Constants.ITEM_VALUE).equals(labelTypeValue)) {
                        buf.append(' ');
                        buf.append(map.get(Constants.ITEM_LABEL));
                        break;
                    }
                }
            }
        }
        return buf.toString();
    }

    protected void buildViewParams() {
        // label
        labelTypeItems = labelTypeHelper.getLabelTypeItemList();

        if (!labelTypeItems.isEmpty() && !indexForm.fields.containsKey(LABEL_FIELD)) {
            final String defaultLabelValue = crawlerProperties.getProperty(Constants.DEFAULT_LABEL_VALUE_PROPERTY,
                    StringUtil.EMPTY);
            if (StringUtil.isNotBlank(defaultLabelValue)) {
                final String[] values = defaultLabelValue.split("\n");
                if (values != null && values.length > 0) {
                    final List<String> list = new ArrayList<String>(values.length);
                    for (final String value : values) {
                        if (StringUtil.isNotBlank(value)) {
                            list.add(value);
                        }
                    }
                    if (!list.isEmpty()) {
                        indexForm.fields.put(LABEL_FIELD, list.toArray(new String[list.size()]));
                    }
                }
            }
        }

        final Map<String, String> labelMap = new LinkedHashMap<String, String>();
        if (!labelTypeItems.isEmpty()) {
            for (final Map<String, String> map : labelTypeItems) {
                labelMap.put(map.get(Constants.ITEM_VALUE), map.get(Constants.ITEM_LABEL));
            }
        }
        request.setAttribute(Constants.LABEL_VALUE_MAP, labelMap);

        if (viewHelper.isUseSession()) {
            final HttpSession session = request.getSession(false);
            if (session != null) {
                final Object resultsPerPage = session.getAttribute(Constants.RESULTS_PER_PAGE);
                if (resultsPerPage != null) {
                    indexForm.num = resultsPerPage.toString();
                }
            }
        }

        Locale locale = request.getLocale();
        if (locale == null) {
            locale = Locale.ENGLISH;
        }
        langItems = systemHelper.getLanguageItems(locale);

        final HttpSession session = request.getSession(false);
        if (session != null) {
            final Object obj = session.getAttribute(SSCConstants.USER_INFO);
            if (obj instanceof LoginInfo) {
                final LoginInfo loginInfo = (LoginInfo) obj;
                username = loginInfo.getUsername();
            }
        }

    }

    protected void buildInitParams() {
        buildInitParamMap(viewHelper.getInitFacetParamMap(), Constants.FACET_QUERY, Constants.FACET_FORM);
        buildInitParamMap(viewHelper.getInitMltParamMap(), Constants.MLT_QUERY, Constants.MLT_FORM);
        buildInitParamMap(viewHelper.getInitGeoParamMap(), Constants.GEO_QUERY, Constants.GEO_FORM);
    }

    protected void buildInitParamMap(final Map<String, String> paramMap, final String queryKey,
            final String formKey) {
        if (!paramMap.isEmpty()) {
            final StringBuilder queryBuf = new StringBuilder(100);
            final StringBuilder formBuf = new StringBuilder(100);
            for (final Map.Entry<String, String> entry : paramMap.entrySet()) {
                queryBuf.append('&');
                queryBuf.append(URLEncoderUtil.encode(entry.getValue()));
                queryBuf.append('=');
                queryBuf.append(URLEncoderUtil.encode(entry.getKey()));
                formBuf.append("<input type=\"hidden\" name=\"");
                formBuf.append(StringEscapeUtils.escapeHtml(entry.getValue()));
                formBuf.append("\" value=\"");
                formBuf.append(StringEscapeUtils.escapeHtml(entry.getKey()));
                formBuf.append("\"/>");
            }
            request.setAttribute(queryKey, queryBuf.toString());
            request.setAttribute(formKey, formBuf.toString());
        }
    }

    protected void normalizePageNum() {
        try {
            final int num = Integer.parseInt(indexForm.num);
            if (num > getMaxPageSize()) {
                // max page size
                indexForm.num = String.valueOf(getMaxPageSize());
            } else if (num <= 0) {
                indexForm.num = String.valueOf(getDefaultPageSize());
            }
        } catch (final NumberFormatException e) {
            indexForm.num = String.valueOf(getDefaultPageSize());
        }
    }

    protected int getDefaultPageSize() {
        return DEFAULT_PAGE_SIZE;
    }

    protected int getMaxPageSize() {
        Object maxPageSize = crawlerProperties.get(Constants.SEARCH_RESULT_MAX_PAGE_SIZE);
        if (maxPageSize == null) {
            return MAX_PAGE_SIZE;
        }
        try {
            return Integer.parseInt(maxPageSize.toString());
        } catch (NumberFormatException e) {
            return MAX_PAGE_SIZE;
        }
    }

    public boolean isOsddLink() {
        return openSearchHelper.hasOpenSearchFile();
    }

    public String getHelpPage() {
        return viewHelper.getPagePath("common/help");
    }

    protected boolean hasFieldInQuery(final Set<String> fieldSet, final String query) {
        final Matcher matcher = FIELD_EXTRACTION_PATTERN.matcher(query);
        if (matcher.matches()) {
            final String field = matcher.replaceFirst("$1");
            if (fieldSet.contains(field)) {
                return true;
            }
            fieldSet.add(field);
        }
        return false;
    }

    protected String getImageMimeType(final File imageFile) {
        final String path = imageFile.getAbsolutePath();
        if (path.endsWith(".png")) {
            return "image/png";
        } else if (path.endsWith(".gif")) {
            return "image/gif";
        } else if (path.endsWith(".jpg") || path.endsWith(".jpeg")) {
            return "image/jpeg";
        } else {
            return "application/octet-stream";
        }
    }

}