Shinsuke Sugaya 11 năm trước cách đây
mục cha
commit
c1447590ff

+ 1 - 1
pom.xml

@@ -826,7 +826,7 @@
     <dependency>
     <dependency>
       <groupId>com.github.jknack</groupId>
       <groupId>com.github.jknack</groupId>
       <artifactId>handlebars</artifactId>
       <artifactId>handlebars</artifactId>
-      <version>1.1.2</version>
+      <version>1.2.1</version>
     </dependency>
     </dependency>
     <dependency>
     <dependency>
       <groupId>org.codehaus.groovy</groupId>
       <groupId>org.codehaus.groovy</groupId>

+ 32 - 0
src/main/java/jp/sf/fess/action/IndexAction.java

@@ -308,11 +308,40 @@ public class IndexAction {
         return "search.jsp";
         return "search.jsp";
     }
     }
 
 
+    @Execute(validator = true, input = "index")
+    public String cache() {
+        Map<String, Object> doc = null;
+        try {
+            doc = searchService.getDocument("docId:" + 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);
+        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")
     @Execute(validator = true, input = "index")
     public String go() throws IOException {
     public String go() throws IOException {
         Map<String, Object> doc = null;
         Map<String, Object> doc = null;
         try {
         try {
             doc = searchService.getDocument("docId:" + indexForm.docId,
             doc = searchService.getDocument("docId:" + indexForm.docId,
+                    queryHelper.getResponseFields(),
                     new String[] { systemHelper.clickCountField });
                     new String[] { systemHelper.clickCountField });
         } catch (final Exception e) {
         } catch (final Exception e) {
             logger.warn("Failed to request: " + indexForm.docId, e);
             logger.warn("Failed to request: " + indexForm.docId, e);
@@ -632,6 +661,7 @@ public class IndexAction {
         try {
         try {
             final Map<String, Object> doc = indexForm.docId == null ? null
             final Map<String, Object> doc = indexForm.docId == null ? null
                     : searchService.getDocument("docId:" + indexForm.docId,
                     : searchService.getDocument("docId:" + indexForm.docId,
+                            queryHelper.getResponseFields(),
                             new String[] { systemHelper.favoriteCountField });
                             new String[] { systemHelper.favoriteCountField });
             final String userCode = userInfoHelper.getUserCode();
             final String userCode = userInfoHelper.getUserCode();
             final String favoriteUrl = doc == null ? null : (String) doc
             final String favoriteUrl = doc == null ? null : (String) doc
@@ -711,6 +741,7 @@ public class IndexAction {
                     .getResultDocIds(indexForm.queryId);
                     .getResultDocIds(indexForm.queryId);
             final List<Map<String, Object>> docList = searchService
             final List<Map<String, Object>> docList = searchService
                     .getDocumentListByDocIds(docIds,
                     .getDocumentListByDocIds(docIds,
+                            queryHelper.getResponseFields(),
                             new String[] { systemHelper.favoriteCountField },
                             new String[] { systemHelper.favoriteCountField },
                             MAX_PAGE_SIZE);
                             MAX_PAGE_SIZE);
             List<String> urlList = new ArrayList<String>(docList.size());
             List<String> urlList = new ArrayList<String>(docList.size());
@@ -853,6 +884,7 @@ public class IndexAction {
         try {
         try {
             documentItems = searchService.getDocumentList(query, pageStart,
             documentItems = searchService.getDocumentList(query, pageStart,
                     pageNum, indexForm.facet, indexForm.geo, indexForm.mlt,
                     pageNum, indexForm.facet, indexForm.geo, indexForm.mlt,
+                    queryHelper.getResponseFields(),
                     queryHelper.getResponseDocValuesFields());
                     queryHelper.getResponseDocValuesFields());
         } catch (final SolrLibQueryException e) {
         } catch (final SolrLibQueryException e) {
             if (logger.isDebugEnabled()) {
             if (logger.isDebugEnabled()) {

+ 2 - 1
src/main/java/jp/sf/fess/action/MobileAction.java

@@ -41,9 +41,9 @@ import jp.sf.fess.util.QueryResponseList;
 
 
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.StringUtils;
 import org.codelibs.core.util.DynamicProperties;
 import org.codelibs.core.util.DynamicProperties;
+import org.codelibs.core.util.StringUtil;
 import org.codelibs.sastruts.core.exception.SSCActionMessagesException;
 import org.codelibs.sastruts.core.exception.SSCActionMessagesException;
 import org.seasar.framework.beans.util.Beans;
 import org.seasar.framework.beans.util.Beans;
-import org.codelibs.core.util.StringUtil;
 import org.seasar.struts.annotation.ActionForm;
 import org.seasar.struts.annotation.ActionForm;
 import org.seasar.struts.annotation.Execute;
 import org.seasar.struts.annotation.Execute;
 import org.seasar.struts.util.RequestUtil;
 import org.seasar.struts.util.RequestUtil;
@@ -150,6 +150,7 @@ public class MobileAction {
         try {
         try {
             documentItems = searchService.getDocumentList(mobileForm.query,
             documentItems = searchService.getDocumentList(mobileForm.query,
                     pageStart, pageNum, null, null, null,
                     pageStart, pageNum, null, null, null,
+                    queryHelper.getResponseFields(),
                     queryHelper.getResponseDocValuesFields());
                     queryHelper.getResponseDocValuesFields());
         } catch (final InvalidQueryException e) {
         } catch (final InvalidQueryException e) {
             if (logger.isDebugEnabled()) {
             if (logger.isDebugEnabled()) {

+ 7 - 3
src/main/java/jp/sf/fess/action/admin/SearchListAction.java

@@ -29,18 +29,19 @@ import jp.sf.fess.ResultOffsetExceededException;
 import jp.sf.fess.crud.util.SAStrutsUtil;
 import jp.sf.fess.crud.util.SAStrutsUtil;
 import jp.sf.fess.form.admin.SearchListForm;
 import jp.sf.fess.form.admin.SearchListForm;
 import jp.sf.fess.helper.JobHelper;
 import jp.sf.fess.helper.JobHelper;
+import jp.sf.fess.helper.QueryHelper;
 import jp.sf.fess.helper.SystemHelper;
 import jp.sf.fess.helper.SystemHelper;
 import jp.sf.fess.service.SearchService;
 import jp.sf.fess.service.SearchService;
 import jp.sf.fess.util.QueryResponseList;
 import jp.sf.fess.util.QueryResponseList;
 
 
 import org.codelibs.core.util.DynamicProperties;
 import org.codelibs.core.util.DynamicProperties;
+import org.codelibs.core.util.StringUtil;
 import org.codelibs.sastruts.core.annotation.Token;
 import org.codelibs.sastruts.core.annotation.Token;
 import org.codelibs.sastruts.core.exception.SSCActionMessagesException;
 import org.codelibs.sastruts.core.exception.SSCActionMessagesException;
 import org.codelibs.solr.lib.SolrGroup;
 import org.codelibs.solr.lib.SolrGroup;
 import org.codelibs.solr.lib.SolrGroupManager;
 import org.codelibs.solr.lib.SolrGroupManager;
 import org.codelibs.solr.lib.policy.QueryType;
 import org.codelibs.solr.lib.policy.QueryType;
 import org.seasar.framework.beans.util.Beans;
 import org.seasar.framework.beans.util.Beans;
-import org.codelibs.core.util.StringUtil;
 import org.seasar.struts.annotation.ActionForm;
 import org.seasar.struts.annotation.ActionForm;
 import org.seasar.struts.annotation.Execute;
 import org.seasar.struts.annotation.Execute;
 import org.seasar.struts.taglib.S2Functions;
 import org.seasar.struts.taglib.S2Functions;
@@ -77,6 +78,9 @@ public class SearchListAction implements Serializable {
     @Resource
     @Resource
     protected SystemHelper systemHelper;
     protected SystemHelper systemHelper;
 
 
+    @Resource
+    protected QueryHelper queryHelper;
+
     @Resource
     @Resource
     protected JobHelper jobHelper;
     protected JobHelper jobHelper;
 
 
@@ -155,8 +159,8 @@ public class SearchListAction implements Serializable {
         final int size = Integer.parseInt(searchListForm.num);
         final int size = Integer.parseInt(searchListForm.num);
         try {
         try {
             documentItems = searchService.getDocumentList(query, offset, size,
             documentItems = searchService.getDocumentList(query, offset, size,
-                    null, null, null, new String[] {
-                            systemHelper.clickCountField,
+                    null, null, null, queryHelper.getResponseFields(),
+                    new String[] { systemHelper.clickCountField,
                             systemHelper.favoriteCountField }, false);
                             systemHelper.favoriteCountField }, false);
         } catch (final InvalidQueryException e) {
         } catch (final InvalidQueryException e) {
             if (logger.isDebugEnabled()) {
             if (logger.isDebugEnabled()) {

+ 1443 - 26
src/main/java/jp/sf/fess/helper/QueryHelper.java

@@ -16,64 +16,1481 @@
 
 
 package jp.sf.fess.helper;
 package jp.sf.fess.helper;
 
 
+/*
+ * Copyright 2009-2014 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.
+ */
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.Set;
 
 
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+
+import jp.sf.fess.Constants;
+import jp.sf.fess.InvalidQueryException;
 import jp.sf.fess.entity.FacetInfo;
 import jp.sf.fess.entity.FacetInfo;
 import jp.sf.fess.entity.GeoInfo;
 import jp.sf.fess.entity.GeoInfo;
 import jp.sf.fess.entity.MoreLikeThisInfo;
 import jp.sf.fess.entity.MoreLikeThisInfo;
 import jp.sf.fess.entity.SearchQuery;
 import jp.sf.fess.entity.SearchQuery;
 import jp.sf.fess.entity.SearchQuery.SortField;
 import jp.sf.fess.entity.SearchQuery.SortField;
+import jp.sf.fess.util.QueryUtil;
 import jp.sf.fess.util.SearchParamMap;
 import jp.sf.fess.util.SearchParamMap;
 
 
-public interface QueryHelper {
+import org.apache.commons.lang.StringUtils;
+import org.codelibs.core.util.StringUtil;
+import org.seasar.framework.container.annotation.tiger.Binding;
+import org.seasar.framework.container.annotation.tiger.BindingType;
+import org.seasar.struts.util.RequestUtil;
+
+public class QueryHelper implements Serializable {
+
+    private static final String LABEL_FIELD = "label";
+
+    private static final String INURL_FIELD = "inurl";
+
+    private static final String TITLE_FIELD = "title";
+
+    private static final String CONTENT_FIELD = "content";
+
+    private static final String NOT_ = "NOT ";
+
+    private static final String AND = "AND";
+
+    private static final String OR = "OR";
+
+    private static final String NOT = "NOT";
+
+    private static final String _TO_ = " TO ";
+
+    private static final String _OR_ = " OR ";
+
+    private static final String _AND_ = " AND ";
+
+    private static final long serialVersionUID = 1L;
+
+    @Binding(bindingType = BindingType.MAY)
+    @Resource
+    protected BrowserTypeHelper browserTypeHelper;
+
+    @Binding(bindingType = BindingType.MAY)
+    @Resource
+    protected RoleQueryHelper roleQueryHelper;
+
+    @Resource
+    protected SystemHelper systemHelper;
+
+    protected Set<String> apiResponseFieldSet;
+
+    protected String[] responseFields = new String[] { "id", "docId", "score",
+            "boost", "contentLength", "host", "site", "lastModified",
+            "mimetype", "filetype_s", "created", TITLE_FIELD, "digest", "url",
+            "clickCount_l_x_dv", "favoriteCount_l_x_dv", "screenshot_s_s",
+            "cid_s_s", "lang_s", "hasCache_s_s" };
+
+    protected String[] cacheResponseFields = new String[] { "id", "docId",
+            "score", "boost", "contentLength", "host", "site", "lastModified",
+            "mimetype", "filetype_s", "created", TITLE_FIELD, "digest", "url",
+            "clickCount_l_x_dv", "favoriteCount_l_x_dv", "screenshot_s_s",
+            "cid_s_s", "lang_s", "cache" };
+
+    protected String[] responseDocValuesFields = new String[] {
+            "clickCount_l_x_dv", "favoriteCount_l_x_dv" };
+
+    protected String[] highlightingFields = new String[] { CONTENT_FIELD };
+
+    protected String[] searchFields = new String[] { "url", "docId", "host",
+            TITLE_FIELD, CONTENT_FIELD, "contentLength", "lastModified",
+            "mimetype", "filetype_s", LABEL_FIELD, "segment",
+            "clickCount_l_x_dv", "favoriteCount_l_x_dv", INURL_FIELD, "lang_s" };
+
+    protected String[] facetFields = new String[] { "url", "host", TITLE_FIELD,
+            CONTENT_FIELD, "contentLength", "lastModified", "mimetype",
+            "filetype_s", LABEL_FIELD, "segment" };
+
+    protected String sortPrefix = "sort:";
+
+    protected String[] supportedSortFields = new String[] { "created",
+            "contentLength", "lastModified", "clickCount_l_x_dv",
+            "favoriteCount_l_x_dv" };
+
+    protected String[] supportedMltFields = new String[] { CONTENT_FIELD,
+            "content_ja" };
+
+    protected String[] supportedAnalysisFields = new String[] { CONTENT_FIELD,
+            "content_ja" };
+
+    protected int highlightSnippetSize = 5;
+
+    protected String shards;
+
+    protected boolean useBigram = true;
+
+    protected String additionalQuery;
+
+    protected int maxFilterQueriesForRole = 3;
+
+    protected int timeAllowed = -1;
+
+    protected Map<String, String[]> requestParameterMap = new HashMap<String, String[]>();
+
+    protected String additionalGeoQuery;
+
+    protected Map<String, String> fieldLanguageMap = new HashMap<String, String>();
+
+    protected int maxSearchResultOffset = 100000;
+
+    protected List<SortField> defaultSortFieldList = new ArrayList<SortField>();
+
+    protected String highlightingPrefix = "hl_";
+
+    protected String minimumShouldMatch = "100%";
+
+    protected FacetInfo defaultFacetInfo;
+
+    protected MoreLikeThisInfo defaultMoreLikeThisInfo;
+
+    protected GeoInfo defaultGeoInfo;
+
+    protected String defaultQueryLanguage;
+
+    protected Map<String, String[]> additionalQueryParamMap = new HashMap<String, String[]>();
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see jp.sf.fess.helper.QueryHelper#build(java.lang.String)
+     */
+    public SearchQuery build(final String query, final boolean envCondition) {
+        String q;
+        if (envCondition && additionalQuery != null
+                && StringUtil.isNotBlank(query)) {
+            q = query + " " + additionalQuery;
+        } else {
+            q = query;
+        }
+
+        final SearchQuery searchQuery = buildQuery(q);
+        if (!searchQuery.queryExists()) {
+            return searchQuery.query(StringUtil.EMPTY);
+        }
+
+        if (browserTypeHelper == null && roleQueryHelper == null
+                || !envCondition) {
+            return searchQuery;
+        }
+
+        StringBuilder queryBuf = new StringBuilder(255);
+        queryBuf.append(searchQuery.getQuery());
+
+        if (browserTypeHelper != null) {
+            searchQuery
+                    .addFilterQuery("type:"
+                            + QueryUtil.escapeValue(browserTypeHelper
+                                    .getBrowserType()));
+        }
+
+        if (roleQueryHelper != null) {
+            final Set<String> roleSet = roleQueryHelper.build();
+            if (roleSet.size() > maxFilterQueriesForRole) {
+                // add query
+                final String sq = queryBuf.toString();
+                queryBuf = new StringBuilder(255);
+                final boolean hasQueries = sq.contains(_AND_)
+                        || sq.contains(_OR_);
+                if (hasQueries) {
+                    queryBuf.append('(');
+                }
+                queryBuf.append(sq);
+                if (hasQueries) {
+                    queryBuf.append(')');
+                }
+                queryBuf.append(_AND_);
+                if (roleSet.size() > 1) {
+                    queryBuf.append('(');
+                }
+                queryBuf.append(getRoleQuery(roleSet));
+                if (roleSet.size() > 1) {
+                    queryBuf.append(')');
+                }
+            } else if (!roleSet.isEmpty()) {
+                // add filter query
+                searchQuery.addFilterQuery(getRoleQuery(roleSet));
+            }
+        }
+
+        return searchQuery.query(queryBuf.toString());
+    }
+
+    private String getRoleQuery(final Set<String> roleList) {
+        final StringBuilder queryBuf = new StringBuilder(255);
+        boolean isFirst = true;
+        for (final String role : roleList) {
+            if (isFirst) {
+                isFirst = false;
+            } else {
+                queryBuf.append(_OR_);
+
+            }
+            queryBuf.append("role:");
+            queryBuf.append(QueryUtil.escapeValue(role));
+        }
+        return queryBuf.toString();
+    }
+
+    protected SearchQuery buildQuery(final String query) {
+        final Map<String, String> sortFieldMap = new LinkedHashMap<String, String>();
+        final List<String> highLightQueryList = new ArrayList<String>();
+        final Map<String, List<String>> fieldLogMap = new HashMap<String, List<String>>();
+        final SearchQuery searchQuery = new SearchQuery();
+
+        final String q = buildQuery(query, sortFieldMap, highLightQueryList,
+                fieldLogMap);
+        String solrQuery;
+        if (q == null || "()".equals(q)) {
+            solrQuery = StringUtil.EMPTY;
+            // } else if (q.startsWith("(") && q.endsWith(")")) {
+            // solrQuery = q.substring(1, q.length() - 1);
+        } else {
+            solrQuery = unbracketQuery(q);
+        }
+        searchQuery.setQuery(solrQuery);
+
+        searchQuery.setMinimumShouldMatch(minimumShouldMatch);
+
+        for (final Map.Entry<String, String> entry : sortFieldMap.entrySet()) {
+            searchQuery.addSortField(entry.getKey(), entry.getValue());
+        }
+        // set queries to request for HighLight
+        final HttpServletRequest request = RequestUtil.getRequest();
+        if (request != null) {
+            request.setAttribute(Constants.HIGHLIGHT_QUERIES,
+                    highLightQueryList.toArray(new String[highLightQueryList
+                            .size()]));
+            request.setAttribute(Constants.FIELD_LOGS, fieldLogMap);
+        }
+        return searchQuery;
+    }
+
+    protected String unbracketQuery(final String query) {
+        if (query.startsWith("(") && query.endsWith(")")) {
+            int count = 0;
+            int depth = 0;
+            int escape = 0;
+            for (int i = 0; i < query.length(); i++) {
+                final char c = query.charAt(i);
+                if (c == '\\') {
+                    escape++;
+                } else {
+                    if (c == '(' && escape % 2 == 0) {
+                        if (depth == 0) {
+                            count++;
+                        }
+                        depth++;
+                    } else if (c == ')' && escape % 2 == 0) {
+                        depth--;
+                    }
+                    escape = 0;
+                }
+            }
+            if (depth == 0 && count == 1) {
+                return unbracketQuery(query.substring(1, query.length() - 1));
+            }
+        }
+        return query;
+
+    }
+
+    protected String buildQuery(final String query,
+            final Map<String, String> sortFieldMap,
+            final List<String> highLightQueryList,
+            final Map<String, List<String>> fieldLogMap) {
+        final List<QueryPart> queryPartList = splitQuery(query, sortFieldMap,
+                highLightQueryList, fieldLogMap);
+        if (queryPartList == null || queryPartList.isEmpty()) {
+            return null;
+        }
+
+        final StringBuilder queryBuf = new StringBuilder(255);
+        final List<String> notOperatorList = new ArrayList<String>();
+        String operator = _AND_;
+        boolean notOperatorFlag = false;
+        int queryOperandCount = 0;
+        int contentOperandCount = 0;
+        final String queryLanguage = getQueryLanguage();
+        for (final QueryPart queryPart : queryPartList) {
+            if (queryPart.isParsed()) {
+                if (queryBuf.length() > 0) {
+                    queryBuf.append(operator);
+                }
+                queryBuf.append(queryPart.getValue());
+                continue;
+            }
+            final String value = queryPart.getValue();
+            boolean nonPrefix = false;
+            // check prefix
+            for (final String field : searchFields) {
+                String prefix = field + ":";
+                if (value.startsWith(prefix)
+                        && value.length() != prefix.length()) {
+                    if (queryBuf.length() > 0 && !notOperatorFlag) {
+                        queryBuf.append(operator);
+                    }
+                    boolean isInUrl = false;
+                    final String targetWord = value.substring(prefix.length());
+                    if (INURL_FIELD.equals(field)) {
+                        prefix = "url:";
+                        isInUrl = true;
+                    }
+                    String fieldLogWord;
+                    if (notOperatorFlag) {
+                        final StringBuilder buf = new StringBuilder(100);
+                        buf.append(prefix);
+                        if (isInUrl) {
+                            buf.append('*');
+                        }
+                        appendQueryValue(buf, targetWord);
+                        if (isInUrl) {
+                            buf.append('*');
+                        }
+                        notOperatorList.add(buf.toString());
+                        notOperatorFlag = false;
+                        fieldLogWord = NOT_ + targetWord;
+                    } else {
+                        queryBuf.append(prefix);
+                        if (isInUrl) {
+                            queryBuf.append('*');
+                        }
+                        appendQueryValue(queryBuf, targetWord);
+                        if (isInUrl) {
+                            queryBuf.append('*');
+                        }
+                        queryOperandCount++;
+                        fieldLogWord = targetWord;
+                    }
+                    nonPrefix = true;
+                    operator = _AND_;
+                    if (!LABEL_FIELD.equals(field)) {
+                        highLightQueryList.add(targetWord);
+                    }
+                    if (fieldLogMap != null) {
+                        addFieldLogValue(fieldLogMap, field, fieldLogWord);
+                    }
+                    break;
+                }
+            }
+
+            // sort
+            if (value.startsWith(sortPrefix)
+                    && value.length() != sortPrefix.length()) {
+                final String[] sortFieldPairs = value.substring(
+                        sortPrefix.length()).split(",");
+                for (final String sortFieldPairStr : sortFieldPairs) {
+                    final String[] sortFieldPair = sortFieldPairStr
+                            .split("\\.");
+                    if (isSupportedSortField(sortFieldPair[0])) {
+                        if (sortFieldPair.length == 1) {
+                            sortFieldMap.put(sortFieldPair[0], Constants.ASC);
+                        } else {
+                            sortFieldMap
+                                    .put(sortFieldPair[0], sortFieldPair[1]);
+                        }
+                    }
+                }
+                continue;
+            }
+
+            if (!nonPrefix) {
+                if (AND.equals(value)) {
+                    operator = _AND_;
+                } else if (OR.equals(value)) {
+                    operator = _OR_;
+                } else if (NOT.equals(value)) {
+                    notOperatorFlag = true;
+                } else if (notOperatorFlag) {
+                    final StringBuilder buf = new StringBuilder(100);
+
+                    buildContentQueryWithLang(buf, value, queryLanguage);
+                    notOperatorList.add(buf.toString());
+
+                    operator = _AND_;
+                    notOperatorFlag = false;
+                    highLightQueryList.add(value);
+
+                    if (fieldLogMap != null) {
+                        addFieldLogValue(fieldLogMap, CONTENT_FIELD, NOT_
+                                + value);
+                    }
+                } else {
+                    // content
+                    if (queryBuf.length() > 0) {
+                        queryBuf.append(operator);
+                    }
+                    buildContentQueryWithLang(queryBuf, value, queryLanguage);
+                    contentOperandCount++;
+
+                    operator = _AND_;
+                    highLightQueryList.add(value);
+
+                    if (fieldLogMap != null) {
+                        addFieldLogValue(fieldLogMap, CONTENT_FIELD, value);
+                    }
+                }
+            }
+        }
+
+        StringBuilder searchQueryBuf = new StringBuilder(255);
+        if (queryBuf.length() > 0) {
+            searchQueryBuf.append(queryBuf.toString());
+            operator = _AND_;
+        } else {
+            operator = StringUtil.EMPTY;
+        }
+        if (!notOperatorList.isEmpty()) {
+            final String q = searchQueryBuf.toString();
+            searchQueryBuf = new StringBuilder(255);
+            final int count = queryOperandCount + contentOperandCount;
+            if (count > 1) {
+                searchQueryBuf.append('(');
+            }
+            searchQueryBuf.append(q);
+            if (count > 1) {
+                searchQueryBuf.append(')');
+            }
+            for (final String notOperator : notOperatorList) {
+                searchQueryBuf.append(operator);
+                searchQueryBuf.append(NOT_);
+                searchQueryBuf.append(notOperator);
+                operator = _AND_;
+            }
+        }
+
+        return searchQueryBuf.toString();
+    }
+
+    private void addFieldLogValue(final Map<String, List<String>> fieldLogMap,
+            final String field, final String targetWord) {
+        List<String> logList = fieldLogMap.get(field);
+        if (logList == null) {
+            logList = new ArrayList<String>();
+            fieldLogMap.put(field, logList);
+        }
+        logList.add(targetWord);
+    }
+
+    protected void appendQueryValue(final StringBuilder buf, final String query) {
+        // check reserved
+        boolean reserved = false;
+        for (final String element : Constants.RESERVED) {
+            if (element.equals(query)) {
+                reserved = true;
+                break;
+            }
+        }
+
+        if (reserved) {
+            buf.append('\\');
+            buf.append(query);
+            return;
+        }
+
+        String value = query;
+        if (useBigram && value.length() == 1
+                && !StringUtils.isAsciiPrintable(value)) {
+            // if using bigram, add ?
+            value = value + '?';
+        }
+
+        String fuzzyValue = null;
+        String proximityValue = null;
+        String caretValue = null;
+        final int tildePos = value.lastIndexOf('~');
+        final int caretPos = value.indexOf('^');
+        if (tildePos > caretPos) {
+            if (tildePos > 0) {
+                final String tildeValue = value.substring(tildePos);
+                if (tildeValue.length() > 1) {
+                    final StringBuilder buf1 = new StringBuilder();
+                    final StringBuilder buf2 = new StringBuilder();
+                    boolean isComma = false;
+                    for (int i = 1; i < tildeValue.length(); i++) {
+                        final char c = tildeValue.charAt(i);
+                        if (c >= '0' && c <= '9') {
+                            if (isComma) {
+                                buf2.append(c);
+                            } else {
+                                buf1.append(c);
+                            }
+                        } else if (c == '.') {
+                            if (isComma) {
+                                break;
+                            } else {
+                                isComma = true;
+                            }
+                        } else {
+                            break;
+                        }
+                    }
+                    if (buf1.length() == 0) {
+                        fuzzyValue = "~";
+                    } else {
+                        final int intValue = Integer.parseInt(buf1.toString());
+                        if (intValue <= 0) {
+                            // fuzzy
+                            buf1.append('.').append(buf2.toString());
+                            fuzzyValue = '~' + buf1.toString();
+                        } else {
+                            // proximity
+                            proximityValue = '~' + Integer.toString(intValue);
+                        }
+                    }
+                } else {
+                    fuzzyValue = "~";
+                }
+
+                value = value.substring(0, tildePos);
+            }
+        } else {
+            if (caretPos > 0) {
+                caretValue = value.substring(caretPos);
+                value = value.substring(0, caretPos);
+            }
+        }
+        if (value.startsWith("[") && value.endsWith("]")) {
+            appendRangeQueryValue(buf, value, '[', ']');
+        } else if (value.startsWith("{") && value.endsWith("}")) {
+            // TODO function
+            appendRangeQueryValue(buf, value, '{', '}');
+        } else {
+            if (proximityValue == null) {
+                buf.append(QueryUtil.escapeValue(value));
+            } else {
+                buf.append('"').append(QueryUtil.escapeValue(value))
+                        .append('"');
+            }
+        }
+
+        if (fuzzyValue != null) {
+            buf.append(fuzzyValue);
+        } else if (proximityValue != null) {
+            buf.append(proximityValue);
+        } else if (caretValue != null) {
+            buf.append(caretValue);
+        }
+    }
+
+    protected void appendRangeQueryValue(final StringBuilder buf,
+            final String value, final char prefix, final char suffix) {
+        final String[] split = value.substring(1, value.length() - 1).split(
+                _TO_);
+        if (split.length == 2 && split[0].length() > 0 && split[1].length() > 0) {
+            final String value1 = split[0].trim();
+            final String value2 = split[1].trim();
+            if ("*".equals(value1) && "*".equals(value2)) {
+                throw new InvalidQueryException(
+                        "errors.invalid_query_str_range", "Invalid range: "
+                                + value);
+            }
+            buf.append(prefix);
+            buf.append(QueryUtil.escapeRangeValue(value1));
+            buf.append(_TO_);
+            buf.append(QueryUtil.escapeRangeValue(value2));
+            buf.append(suffix);
+        } else {
+            throw new InvalidQueryException("errors.invalid_query_str_range",
+                    "Invalid range: " + value);
+        }
+    }
+
+    private boolean isSupportedSortField(final String field) {
+        for (final String f : supportedSortFields) {
+            if (f.equals(field)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected List<QueryPart> splitQuery(final String query,
+            final Map<String, String> sortFieldMap,
+            final List<String> highLightQueryList,
+            final Map<String, List<String>> fieldLogMap) {
+        final List<QueryPart> valueList = new ArrayList<QueryPart>();
+        StringBuilder buf = new StringBuilder();
+        boolean quoted = false;
+        int parenthesis = 0;
+        int squareBracket = 0;
+        int curlyBracket = 0;
+        char oldChar = 0;
+        for (int i = 0; i < query.length(); i++) {
+            final char c = query.charAt(i);
+            if (oldChar == '\\'
+                    && (c == '"' || c == '(' || c == '{' || c == '[')) {
+                buf.append(c);
+            } else {
+                if (oldChar == '\\') {
+                    buf.append('\\');
+                }
+                switch (c) {
+                case '(':
+                    buf.append(c);
+                    if (!quoted && squareBracket == 0 && curlyBracket == 0) {
+                        parenthesis++;
+                    }
+                    break;
+                case ')':
+                    buf.append(c);
+                    if (!quoted && squareBracket == 0 && curlyBracket == 0) {
+                        parenthesis--;
+                    }
+                    break;
+                case '[':
+                    buf.append(c);
+                    if (!quoted && parenthesis == 0 && curlyBracket == 0) {
+                        squareBracket++;
+                    }
+                    break;
+                case ']':
+                    buf.append(c);
+                    if (!quoted && parenthesis == 0 && curlyBracket == 0) {
+                        squareBracket--;
+                    }
+                    break;
+                case '{':
+                    buf.append(c);
+                    if (!quoted && parenthesis == 0 && squareBracket == 0) {
+                        curlyBracket++;
+                    }
+                    break;
+                case '}':
+                    buf.append(c);
+                    if (!quoted && parenthesis == 0 && squareBracket == 0) {
+                        curlyBracket--;
+                    }
+                    break;
+                case '"':
+                    if (parenthesis == 0 && curlyBracket == 0
+                            && squareBracket == 0) {
+                        quoted ^= true;
+                    } else {
+                        buf.append(c);
+                    }
+                    break;
+                case '\\':
+                    break;
+                case ' ':
+                case '\u3000':
+                    if (quoted || curlyBracket > 0 || squareBracket > 0
+                            || parenthesis > 0) {
+                        buf.append(c);
+                    } else {
+                        if (buf.length() > 0) {
+                            addQueryPart(buf.toString(), valueList,
+                                    sortFieldMap, highLightQueryList,
+                                    fieldLogMap);
+                        }
+                        buf = new StringBuilder();
+                    }
+                    break;
+                default:
+                    buf.append(c);
+                    break;
+                }
+            }
+            oldChar = c;
+        }
+        if (oldChar == '\\') {
+            buf.append('\\');
+        }
+        if (quoted) {
+            throw new InvalidQueryException("errors.invalid_query_quoted",
+                    "Invalid quoted: " + query);
+        } else if (curlyBracket > 0) {
+            throw new InvalidQueryException(
+                    "errors.invalid_query_curly_bracket",
+                    "Invalid curly bracket: " + query);
+        } else if (squareBracket > 0) {
+            throw new InvalidQueryException(
+                    "errors.invalid_query_square_bracket",
+                    "Invalid square bracket: " + query);
+        } else if (parenthesis > 0) {
+            throw new InvalidQueryException("errors.invalid_query_parenthesis",
+                    "Invalid parenthesis: " + query);
+        }
+        if (buf.length() > 0) {
+            addQueryPart(buf.toString(), valueList, sortFieldMap,
+                    highLightQueryList, fieldLogMap);
+        }
+        return valueList;
+    }
+
+    private void addQueryPart(final String str,
+            final List<QueryPart> valueList,
+            final Map<String, String> sortFieldMap,
+            final List<String> highLightQueryList,
+            final Map<String, List<String>> fieldLogMap) {
+        if (str.startsWith("[") || str.startsWith("{")) {
+            valueList.add(new QueryPart(str.trim()));
+        } else if (str.startsWith("(") && str.endsWith(")") && str.length() > 2) {
+            final String q = str.substring(1, str.length() - 1);
+            if (sortFieldMap != null && highLightQueryList != null) {
+                final String innerQuery = buildQuery(q, sortFieldMap,
+                        highLightQueryList, fieldLogMap);
+                if (StringUtil.isNotBlank(innerQuery)) {
+                    valueList.add(new QueryPart("(" + innerQuery + ")", true));
+                }
+            } else {
+                // facet query
+                final String innerQuery = buildFacetQuery(q);
+                if (StringUtil.isNotBlank(innerQuery)) {
+                    valueList.add(new QueryPart("(" + innerQuery + ")", true));
+                }
+            }
+        } else {
+            valueList.add(new QueryPart(str.trim()));
+        }
+    }
+
+    public boolean isFacetField(final String field) {
+        if (StringUtil.isBlank(field)) {
+            return false;
+        }
+        boolean flag = false;
+        for (final String f : facetFields) {
+            if (field.equals(f)) {
+                flag = true;
+            }
+        }
+        return flag;
+    }
+
+    public String buildFacetQuery(final String query) {
+        final String q = buildFacetQueryInternal(query);
+        String solrQuery;
+        if (q == null || "()".equals(q)) {
+            solrQuery = StringUtil.EMPTY;
+        } else {
+            solrQuery = unbracketQuery(q);
+        }
+        return solrQuery;
+    }
+
+    protected String buildFacetQueryInternal(final String query) {
+        final List<QueryPart> queryPartList = splitQuery(query, null, null,
+                null);
+        if (queryPartList.isEmpty()) {
+            return StringUtil.EMPTY;
+        }
+
+        final StringBuilder queryBuf = new StringBuilder(255);
+        final List<String> notOperatorList = new ArrayList<String>();
+        String operator = _AND_;
+        boolean notOperatorFlag = false;
+        int queryOperandCount = 0;
+        int contentOperandCount = 0;
+        final String queryLanguage = getQueryLanguage();
+        for (final QueryPart queryPart : queryPartList) {
+            if (queryPart.isParsed()) {
+                if (queryBuf.length() > 0) {
+                    queryBuf.append(operator);
+                }
+                queryBuf.append(queryPart.getValue());
+                continue;
+            }
+            final String value = queryPart.getValue();
+            boolean nonPrefix = false;
+            // check prefix
+            for (final String field : facetFields) {
+                final String prefix = field + ":";
+                if (value.startsWith(prefix)
+                        && value.length() != prefix.length()) {
+                    if (queryBuf.length() > 0) {
+                        queryBuf.append(operator);
+                    }
+                    final String targetWord = value.substring(prefix.length());
+                    if (notOperatorFlag) {
+                        final StringBuilder buf = new StringBuilder(100);
+                        buf.append(prefix);
+                        appendQueryValue(buf, targetWord);
+                        notOperatorList.add(buf.toString());
+                        notOperatorFlag = false;
+                    } else {
+                        queryBuf.append(prefix);
+                        appendQueryValue(queryBuf, targetWord);
+                        queryOperandCount++;
+                    }
+                    nonPrefix = true;
+                    operator = _AND_;
+                    break;
+                }
+            }
+
+            // sort
+            if (value.startsWith(sortPrefix)
+                    && value.length() != sortPrefix.length()) {
+                // skip
+                continue;
+            }
+
+            if (!nonPrefix) {
+                if (AND.equals(value)) {
+                    operator = _AND_;
+                } else if (OR.equals(value)) {
+                    operator = _OR_;
+                } else if (NOT.equals(value)) {
+                    notOperatorFlag = true;
+                } else if (notOperatorFlag) {
+                    final StringBuilder buf = new StringBuilder(100);
+
+                    buildContentQueryWithLang(buf, value, queryLanguage);
+                    notOperatorList.add(buf.toString());
+
+                    operator = _AND_;
+                    notOperatorFlag = false;
+                } else {
+                    // content
+                    if (queryBuf.length() > 0) {
+                        queryBuf.append(operator);
+                    }
+                    buildContentQueryWithLang(queryBuf, value, queryLanguage);
+                    contentOperandCount++;
+
+                    operator = _AND_;
+                }
+            }
+        }
+
+        StringBuilder searchQueryBuf = new StringBuilder(255);
+        if (queryBuf.length() > 0) {
+            searchQueryBuf.append(queryBuf.toString());
+            operator = _AND_;
+        } else {
+            operator = StringUtil.EMPTY;
+        }
+        if (!notOperatorList.isEmpty()) {
+            final String q = searchQueryBuf.toString();
+            searchQueryBuf = new StringBuilder(255);
+            final int count = queryOperandCount + contentOperandCount;
+            if (count > 1) {
+                searchQueryBuf.append('(');
+            }
+            searchQueryBuf.append(q);
+            if (count > 1) {
+                searchQueryBuf.append(')');
+            }
+            for (final String notOperator : notOperatorList) {
+                searchQueryBuf.append(operator);
+                searchQueryBuf.append(NOT_);
+                searchQueryBuf.append(notOperator);
+                operator = _AND_;
+            }
+        }
+
+        return searchQueryBuf.toString();
+    }
+
+    protected void buildContentQueryWithLang(final StringBuilder buf,
+            final String value, final String queryLanguage) {
+        buf.append('(');
+        buf.append(TITLE_FIELD).append(':');
+        appendQueryValue(buf, value);
+        buf.append(_OR_);
+        buf.append(CONTENT_FIELD).append(':');
+        appendQueryValue(buf, value);
+        if (StringUtil.isNotBlank(queryLanguage)) {
+            buf.append(_OR_);
+            buf.append("content_");
+            buf.append(queryLanguage);
+            buf.append(':');
+            appendQueryValue(buf, value);
+        }
+        buf.append(')');
+    }
+
+    protected String getQueryLanguage() {
+        final String[] supportedLanguages = systemHelper
+                .getSupportedLanguages();
+        if (supportedLanguages.length == 0) {
+            return null;
+        }
+        if (defaultQueryLanguage != null) {
+            return defaultQueryLanguage;
+        }
+        final HttpServletRequest request = RequestUtil.getRequest();
+        if (request == null) {
+            return null;
+        }
+        final Locale locale = request.getLocale();
+        if (locale == null) {
+            return null;
+        }
+        final String language = locale.getLanguage();
+        final String country = locale.getCountry();
+        if (StringUtil.isNotBlank(language)) {
+            if (StringUtil.isNotBlank(country)) {
+                final String lang = language + "_" + country;
+                for (final String value : supportedLanguages) {
+                    if (value.equals(lang)) {
+                        final String fieldLang = fieldLanguageMap.get(value);
+                        if (fieldLang == null) {
+                            return value;
+                        } else {
+                            return fieldLang;
+                        }
+                    }
+                }
+            }
+            for (final String value : supportedLanguages) {
+                if (value.equals(language)) {
+                    final String fieldLang = fieldLanguageMap.get(value);
+                    if (fieldLang == null) {
+                        return value;
+                    } else {
+                        return fieldLang;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    public boolean isFacetSortValue(final String sort) {
+        return "count".equals(sort) || "index".equals(sort);
+    }
+
+    public String buildOptionQuery(final SearchParamMap optionMap) {
+        if (optionMap == null) {
+            return StringUtil.EMPTY;
+        }
+
+        final StringBuilder buf = new StringBuilder();
+
+        final String[] qs = optionMap.get(Constants.OPTION_QUERY_Q);
+        if (qs != null) {
+            for (final String q : qs) {
+                if (StringUtil.isNotBlank(q)) {
+                    buf.append(' ');
+                    buf.append(q);
+                }
+            }
+        }
+
+        final String[] cqs = optionMap.get(Constants.OPTION_QUERY_CQ);
+        if (cqs != null) {
+            for (final String cq : cqs) {
+                if (StringUtil.isNotBlank(cq)) {
+                    buf.append(' ');
+                    char split = 0;
+                    final List<QueryPart> partList = splitQuery(
+                            cq.indexOf('"') >= 0 ? cq : "\"" + cq + "\"", null,
+                            null, null);
+                    for (final QueryPart part : partList) {
+                        if (split == 0) {
+                            split = ' ';
+                        } else {
+                            buf.append(split);
+                        }
+                        final String value = part.getValue();
+                        buf.append('"');
+                        buf.append(value);
+                        buf.append('"');
+                    }
+                }
+            }
+        }
+
+        final String[] oqs = optionMap.get(Constants.OPTION_QUERY_OQ);
+        if (oqs != null) {
+            for (final String oq : oqs) {
+                if (StringUtil.isNotBlank(oq)) {
+                    buf.append(' ');
+                    final List<QueryPart> partList = splitQuery(oq, null, null,
+                            null);
+                    final boolean append = partList.size() > 1
+                            && optionMap.size() > 1;
+                    if (append) {
+                        buf.append('(');
+                    }
+                    String split = null;
+                    for (final QueryPart part : partList) {
+                        if (split == null) {
+                            split = _OR_;
+                        } else {
+                            buf.append(split);
+                        }
+                        final String value = part.getValue();
+                        final boolean hasSpace = value.matches(".*\\s.*");
+                        if (hasSpace) {
+                            buf.append('"');
+                        }
+                        buf.append(value);
+                        if (hasSpace) {
+                            buf.append('"');
+                        }
+                    }
+                    if (append) {
+                        buf.append(')');
+                    }
+                }
+            }
+        }
+
+        final String[] nqs = optionMap.get(Constants.OPTION_QUERY_NQ);
+        if (nqs != null) {
+            for (final String nq : nqs) {
+                if (StringUtil.isNotBlank(nq)) {
+                    buf.append(' ');
+                    String split = StringUtil.EMPTY;
+                    final List<QueryPart> partList = splitQuery(nq, null, null,
+                            null);
+                    for (final QueryPart part : partList) {
+                        buf.append(split);
+                        if (split.length() == 0) {
+                            split = " ";
+                        }
+                        buf.append(NOT_);
+                        final String value = part.getValue();
+                        final boolean hasSpace = value.matches(".*\\s.*");
+                        if (hasSpace) {
+                            buf.append('"');
+                        }
+                        buf.append(value);
+                        if (hasSpace) {
+                            buf.append('"');
+                        }
+                    }
+                }
+            }
+        }
+
+        return buf.toString().trim();
+    }
+
+    public void setApiResponseFields(final String[] fields) {
+        apiResponseFieldSet = new HashSet<String>();
+        for (final String field : fields) {
+            apiResponseFieldSet.add(field);
+        }
+    }
+
+    public boolean isApiResponseField(final String field) {
+        if (apiResponseFieldSet == null) {
+            return true;
+        }
+        return apiResponseFieldSet.contains(field);
+    }
+
+    /**
+     * @return the responseFields
+     */
+    public String[] getResponseFields() {
+        return responseFields;
+    }
+
+    /**
+     * @param responseFields the responseFields to set
+     */
+    public void setResponseFields(final String[] responseFields) {
+        this.responseFields = responseFields;
+    }
+
+    public String[] getCacheResponseFields() {
+        return cacheResponseFields;
+    }
+
+    public void setCacheResponseFields(final String[] cacheResponseFields) {
+        this.cacheResponseFields = cacheResponseFields;
+    }
+
+    public String[] getResponseDocValuesFields() {
+        return responseDocValuesFields;
+    }
+
+    public void setResponseDocValuesFields(
+            final String[] responseDocValuesFields) {
+        this.responseDocValuesFields = responseDocValuesFields;
+    }
+
+    /**
+     * @return the highlightingFields
+     */
+    public String[] getHighlightingFields() {
+        return highlightingFields;
+    }
+
+    /**
+     * @param highlightingFields the highlightingFields to set
+     */
+    public void setHighlightingFields(final String[] highlightingFields) {
+        this.highlightingFields = highlightingFields;
+    }
+
+    /**
+     * @return the supportedFields
+     */
+    public String[] getSearchFields() {
+        return searchFields;
+    }
+
+    /**
+     * @param supportedFields the supportedFields to set
+     */
+    public void setSearchFields(final String[] supportedFields) {
+        searchFields = supportedFields;
+    }
+
+    /**
+     * @return the facetFields
+     */
+    public String[] getFacetFields() {
+        return facetFields;
+    }
+
+    /**
+     * @param facetFields the facetFields to set
+     */
+    public void setFacetFields(final String[] facetFields) {
+        this.facetFields = facetFields;
+    }
+
+    /**
+     * @return the sortPrefix
+     */
+    public String getSortPrefix() {
+        return sortPrefix;
+    }
+
+    /**
+     * @param sortPrefix the sortPrefix to set
+     */
+    public void setSortPrefix(final String sortPrefix) {
+        this.sortPrefix = sortPrefix;
+    }
+
+    /**
+     * @return the supportedSortFields
+     */
+    public String[] getSupportedSortFields() {
+        return supportedSortFields;
+    }
+
+    /**
+     * @param supportedSortFields the supportedSortFields to set
+     */
+    public void setSupportedSortFields(final String[] supportedSortFields) {
+        this.supportedSortFields = supportedSortFields;
+    }
+
+    /**
+     * @return the highlightSnippetSize
+     */
+    public int getHighlightSnippetSize() {
+        return highlightSnippetSize;
+    }
+
+    /**
+     * @param highlightSnippetSize the highlightSnippetSize to set
+     */
+    public void setHighlightSnippetSize(final int highlightSnippetSize) {
+        this.highlightSnippetSize = highlightSnippetSize;
+    }
+
+    /**
+     * @return the shards
+     */
+    public String getShards() {
+        return shards;
+    }
+
+    /**
+     * @param shards the shards to set
+     */
+    public void setShards(final String shards) {
+        this.shards = shards;
+    }
+
+    /**
+     * @return the useBigram
+     */
+    public boolean isUseBigram() {
+        return useBigram;
+    }
+
+    /**
+     * @param useBigram the useBigram to set
+     */
+    public void setUseBigram(final boolean useBigram) {
+        this.useBigram = useBigram;
+    }
+
+    /**
+     * @return the additionalQuery
+     */
+    public String getAdditionalQuery() {
+        return additionalQuery;
+    }
+
+    /**
+     * @param additionalQuery the additionalQuery to set
+     */
+    public void setAdditionalQuery(final String additionalQuery) {
+        this.additionalQuery = additionalQuery;
+    }
+
+    public int getMaxFilterQueriesForRole() {
+        return maxFilterQueriesForRole;
+    }
+
+    public void setMaxFilterQueriesForRole(final int maxFilterQuerysForRole) {
+        maxFilterQueriesForRole = maxFilterQuerysForRole;
+    }
+
+    /**
+     * @return the timeAllowed
+     */
+    public int getTimeAllowed() {
+        return timeAllowed;
+    }
+
+    /**
+     * @param timeAllowed the timeAllowed to set
+     */
+    public void setTimeAllowed(final int timeAllowed) {
+        this.timeAllowed = timeAllowed;
+    }
+
+    public void addRequestParameter(final String name, final String... values) {
+        requestParameterMap.put(name, values);
+    }
+
+    public void addRequestParameter(final String name, final String value) {
+        if (value != null) {
+            requestParameterMap.put(name, new String[] { value });
+        }
+    }
+
+    public Set<Entry<String, String[]>> getRequestParameterSet() {
+        return requestParameterMap.entrySet();
+    }
+
+    public String getAdditionalGeoQuery() {
+        return additionalGeoQuery;
+    }
+
+    public void setAdditionalGeoQuery(final String additionalGeoQuery) {
+        this.additionalGeoQuery = additionalGeoQuery;
+    }
+
+    public void addFieldLanguage(final String lang, final String fieldLang) {
+        fieldLanguageMap.put(lang, fieldLang);
+    }
+
+    public int getMaxSearchResultOffset() {
+        return maxSearchResultOffset;
+    }
+
+    public void setMaxSearchResultOffset(final int maxSearchResultOffset) {
+        this.maxSearchResultOffset = maxSearchResultOffset;
+    }
+
+    public void addDefaultSortField(final String fieldName, final String order) {
+        final SortField sortField = new SortField();
+        sortField.setField(fieldName);
+        sortField
+                .setOrder(Constants.ASC.equalsIgnoreCase(order) ? Constants.ASC
+                        : Constants.DESC);
+        defaultSortFieldList.add(sortField);
+    }
+
+    public boolean hasDefaultSortFields() {
+        return !defaultSortFieldList.isEmpty();
+    }
+
+    public SortField[] getDefaultSortFields() {
+        return defaultSortFieldList.toArray(new SortField[defaultSortFieldList
+                .size()]);
+    }
+
+    public void setHighlightingPrefix(final String highlightingPrefix) {
+        this.highlightingPrefix = highlightingPrefix;
+    }
+
+    public String getHighlightingPrefix() {
+        return highlightingPrefix;
+    }
+
+    public String[] getSupportedMltFields() {
+        return supportedMltFields;
+    }
+
+    public void setSupportedMltFields(final String[] supportedMltFields) {
+        this.supportedMltFields = supportedMltFields;
+    }
 
 
-    SearchQuery build(String query, boolean envCondition);
+    public String getMoreLikeThisField(final String[] fields) {
+        if (fields == null || fields.length == 0) {
+            return null;
+        }
+        final List<String> list = new ArrayList<String>();
+        for (final String field : fields) {
+            if (StringUtil.isNotBlank(field)) {
+                for (final String f : field.split(",")) {
+                    final String value = f.trim();
+                    for (final String supported : supportedMltFields) {
+                        if (supported.equals(value)) {
+                            list.add(value);
+                        }
+                    }
+                }
+            }
+        }
+        if (list.isEmpty()) {
+            return null;
+        }
+        return StringUtils.join(list, ',');
+    }
 
 
-    String[] getResponseFields();
+    public String getMinimumShouldMatch() {
+        return minimumShouldMatch;
+    }
 
 
-    String[] getResponseDocValuesFields();
+    public void setMinimumShouldMatch(final String minimumShouldMatch) {
+        this.minimumShouldMatch = minimumShouldMatch;
+    }
 
 
-    String[] getHighlightingFields();
+    public boolean isAnalysisFieldName(final String fieldName) {
+        for (final String f : supportedAnalysisFields) {
+            if (f.endsWith(fieldName)) {
+                return true;
+            }
+        }
+        return false;
+    }
 
 
-    int getHighlightSnippetSize();
+    public FacetInfo getDefaultFacetInfo() {
+        return defaultFacetInfo;
+    }
 
 
-    String getShards();
+    public void setDefaultFacetInfo(final FacetInfo defaultFacetInfo) {
+        this.defaultFacetInfo = defaultFacetInfo;
+    }
 
 
-    boolean isFacetField(String f);
+    public MoreLikeThisInfo getDefaultMoreLikeThisInfo() {
+        return defaultMoreLikeThisInfo;
+    }
 
 
-    String buildFacetQuery(String query);
+    public void setDefaultMoreLikeThisInfo(
+            final MoreLikeThisInfo defaultMoreLikeThisInfo) {
+        this.defaultMoreLikeThisInfo = defaultMoreLikeThisInfo;
+    }
 
 
-    boolean isFacetSortValue(String sort);
+    public GeoInfo getDefaultGeoInfo() {
+        return defaultGeoInfo;
+    }
 
 
-    String buildOptionQuery(SearchParamMap options);
+    public void setDefaultGeoInfo(final GeoInfo defaultGeoInfo) {
+        this.defaultGeoInfo = defaultGeoInfo;
+    }
 
 
-    int getTimeAllowed();
+    public String getDefaultQueryLanguage() {
+        return defaultQueryLanguage;
+    }
 
 
-    Set<Entry<String, String[]>> getRequestParameterSet();
+    public void setDefaultQueryLanguage(final String defaultQueryLanguage) {
+        this.defaultQueryLanguage = defaultQueryLanguage;
+    }
 
 
-    String getAdditionalGeoQuery();
+    public Map<String, String[]> getQueryParamMap() {
+        if (additionalQueryParamMap.isEmpty()) {
+            return additionalQueryParamMap;
+        }
 
 
-    int getMaxSearchResultOffset();
+        final HttpServletRequest request = RequestUtil.getRequest();
+        final Map<String, String[]> queryParamMap = new HashMap<String, String[]>();
+        for (final Map.Entry<String, String[]> entry : additionalQueryParamMap
+                .entrySet()) {
+            final String[] values = entry.getValue();
+            final String[] newValues = new String[values.length];
+            for (int i = 0; i < values.length; i++) {
+                final String value = values[i];
+                if (value.length() > 1 && value.charAt(0) == '$'
+                        && request != null) {
+                    final String param = request.getParameter(value
+                            .substring(1));
+                    if (StringUtil.isNotBlank(param)) {
+                        newValues[i] = param;
+                    } else {
+                        newValues[i] = StringUtil.EMPTY;
+                    }
+                } else {
+                    newValues[i] = value;
+                }
+            }
+            queryParamMap.put(entry.getKey(), newValues);
+        }
 
 
-    boolean hasDefaultSortFields();
+        return queryParamMap;
+    }
 
 
-    SortField[] getDefaultSortFields();
+    public void addQueryParam(final String key, final String[] values) {
+        additionalQueryParamMap.put(key, values);
+    }
 
 
-    String getHighlightingPrefix();
+    public static class QueryPart {
+        protected String value;
 
 
-    String getMoreLikeThisField(String[] field);
+        protected boolean parsed;
 
 
-    boolean isAnalysisFieldName(String fieldName);
+        public QueryPart(final String value) {
+            this(value, false);
+        }
 
 
-    FacetInfo getDefaultFacetInfo();
+        public QueryPart(final String value, final boolean parsed) {
+            this.value = value;
+            this.parsed = parsed;
+        }
 
 
-    MoreLikeThisInfo getDefaultMoreLikeThisInfo();
+        /**
+         * @return the parsed
+         */
+        public boolean isParsed() {
+            return parsed;
+        }
 
 
-    GeoInfo getDefaultGeoInfo();
+        /**
+         * @return the value
+         */
+        public String getValue() {
+            return value;
+        }
 
 
-    Map<String, String[]> getQueryParamMap();
+        /*
+         * (non-Javadoc)
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return "QueryPart [value=" + value + ", parsed=" + parsed + "]";
+        }
+    }
 
 
-    boolean isApiResponseField(String field);
-}
+}

+ 31 - 0
src/main/java/jp/sf/fess/helper/ViewHelper.java

@@ -36,6 +36,7 @@ import jp.sf.fess.Constants;
 import jp.sf.fess.FessSystemException;
 import jp.sf.fess.FessSystemException;
 import jp.sf.fess.entity.FacetQueryView;
 import jp.sf.fess.entity.FacetQueryView;
 import jp.sf.fess.helper.UserAgentHelper.UserAgentType;
 import jp.sf.fess.helper.UserAgentHelper.UserAgentType;
+import jp.sf.fess.util.ResourceUtil;
 
 
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.StringUtils;
 import org.codelibs.core.util.DynamicProperties;
 import org.codelibs.core.util.DynamicProperties;
@@ -45,11 +46,21 @@ import org.seasar.robot.util.CharUtil;
 import org.seasar.struts.taglib.S2Functions;
 import org.seasar.struts.taglib.S2Functions;
 import org.seasar.struts.util.RequestUtil;
 import org.seasar.struts.util.RequestUtil;
 import org.seasar.struts.util.ServletContextUtil;
 import org.seasar.struts.util.ServletContextUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.jknack.handlebars.Context;
+import com.github.jknack.handlebars.Handlebars;
+import com.github.jknack.handlebars.Template;
+import com.github.jknack.handlebars.io.FileTemplateLoader;
 
 
 public class ViewHelper implements Serializable {
 public class ViewHelper implements Serializable {
 
 
     private static final long serialVersionUID = 1L;
     private static final long serialVersionUID = 1L;
 
 
+    private static final Logger logger = LoggerFactory
+            .getLogger(ViewHelper.class);
+
     protected static final String GOOGLE_MOBILE_TRANSCODER_LINK = "http://www.google.co.jp/gwt/n?u=";
     protected static final String GOOGLE_MOBILE_TRANSCODER_LINK = "http://www.google.co.jp/gwt/n?u=";
 
 
     @Resource
     @Resource
@@ -94,6 +105,8 @@ public class ViewHelper implements Serializable {
 
 
     private final List<FacetQueryView> facetQueryViewList = new ArrayList<FacetQueryView>();
     private final List<FacetQueryView> facetQueryViewList = new ArrayList<FacetQueryView>();
 
 
+    public String cacheTemplateName = "cache";
+
     private String getString(final Map<String, Object> doc, final String key) {
     private String getString(final Map<String, Object> doc, final String key) {
         final Object value = doc.get(key);
         final Object value = doc.get(key);
         if (value == null) {
         if (value == null) {
@@ -396,6 +409,23 @@ public class ViewHelper implements Serializable {
         return file.isFile();
         return file.isFile();
     }
     }
 
 
+    public String createCacheContent(final Map<String, Object> doc) {
+
+        final FileTemplateLoader loader = new FileTemplateLoader(new File(
+                ResourceUtil.getViewTemplatePath(StringUtil.EMPTY)));
+        final Handlebars handlebars = new Handlebars(loader);
+
+        try {
+            final Template template = handlebars.compile(cacheTemplateName);
+            final Context hbsContext = Context.newContext(doc);
+            return template.apply(hbsContext);
+        } catch (final Exception e) {
+            logger.warn("Failed to create a cache response.", e);
+        }
+
+        return null;
+    }
+
     public boolean isUseSession() {
     public boolean isUseSession() {
         return useSession;
         return useSession;
     }
     }
@@ -435,4 +465,5 @@ public class ViewHelper implements Serializable {
     public List<FacetQueryView> getFacetQueryViewList() {
     public List<FacetQueryView> getFacetQueryViewList() {
         return facetQueryViewList;
         return facetQueryViewList;
     }
     }
+
 }
 }

+ 0 - 1495
src/main/java/jp/sf/fess/helper/impl/QueryHelperImpl.java

@@ -1,1495 +0,0 @@
-/*
- * Copyright 2009-2014 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 jp.sf.fess.helper.impl;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-import javax.annotation.Resource;
-import javax.servlet.http.HttpServletRequest;
-
-import jp.sf.fess.Constants;
-import jp.sf.fess.InvalidQueryException;
-import jp.sf.fess.entity.FacetInfo;
-import jp.sf.fess.entity.GeoInfo;
-import jp.sf.fess.entity.MoreLikeThisInfo;
-import jp.sf.fess.entity.SearchQuery;
-import jp.sf.fess.entity.SearchQuery.SortField;
-import jp.sf.fess.helper.BrowserTypeHelper;
-import jp.sf.fess.helper.QueryHelper;
-import jp.sf.fess.helper.RoleQueryHelper;
-import jp.sf.fess.helper.SystemHelper;
-import jp.sf.fess.util.QueryUtil;
-import jp.sf.fess.util.SearchParamMap;
-
-import org.apache.commons.lang.StringUtils;
-import org.codelibs.core.util.StringUtil;
-import org.seasar.framework.container.annotation.tiger.Binding;
-import org.seasar.framework.container.annotation.tiger.BindingType;
-import org.seasar.struts.util.RequestUtil;
-
-public class QueryHelperImpl implements QueryHelper, Serializable {
-
-    private static final String LABEL_FIELD = "label";
-
-    private static final String INURL_FIELD = "inurl";
-
-    private static final String TITLE_FIELD = "title";
-
-    private static final String CONTENT_FIELD = "content";
-
-    private static final String NOT_ = "NOT ";
-
-    private static final String AND = "AND";
-
-    private static final String OR = "OR";
-
-    private static final String NOT = "NOT";
-
-    private static final String _TO_ = " TO ";
-
-    private static final String _OR_ = " OR ";
-
-    private static final String _AND_ = " AND ";
-
-    private static final long serialVersionUID = 1L;
-
-    @Binding(bindingType = BindingType.MAY)
-    @Resource
-    protected BrowserTypeHelper browserTypeHelper;
-
-    @Binding(bindingType = BindingType.MAY)
-    @Resource
-    protected RoleQueryHelper roleQueryHelper;
-
-    @Resource
-    protected SystemHelper systemHelper;
-
-    protected Set<String> apiResponseFieldSet;
-
-    protected String[] responseFields = new String[] { "id", "docId", "score",
-            "boost", "contentLength", "host", "site", "lastModified",
-            "mimetype", "filetype_s", "created", TITLE_FIELD, "digest", "url",
-            "clickCount_l_x_dv", "favoriteCount_l_x_dv", "screenshot_s_s",
-            "cid_s_s", "lang_s", "hasCache_s_s" };
-
-    protected String[] responseDocValuesFields = new String[] {
-            "clickCount_l_x_dv", "favoriteCount_l_x_dv" };
-
-    protected String[] highlightingFields = new String[] { CONTENT_FIELD };
-
-    protected String[] searchFields = new String[] { "url", "docId", "host",
-            TITLE_FIELD, CONTENT_FIELD, "contentLength", "lastModified",
-            "mimetype", "filetype_s", LABEL_FIELD, "segment",
-            "clickCount_l_x_dv", "favoriteCount_l_x_dv", INURL_FIELD, "lang_s" };
-
-    protected String[] facetFields = new String[] { "url", "host", TITLE_FIELD,
-            CONTENT_FIELD, "contentLength", "lastModified", "mimetype",
-            "filetype_s", LABEL_FIELD, "segment" };
-
-    protected String sortPrefix = "sort:";
-
-    protected String[] supportedSortFields = new String[] { "created",
-            "contentLength", "lastModified", "clickCount_l_x_dv",
-            "favoriteCount_l_x_dv" };
-
-    protected String[] supportedMltFields = new String[] { CONTENT_FIELD,
-            "content_ja" };
-
-    protected String[] supportedAnalysisFields = new String[] { CONTENT_FIELD,
-            "content_ja" };
-
-    protected int highlightSnippetSize = 5;
-
-    protected String shards;
-
-    protected boolean useBigram = true;
-
-    protected String additionalQuery;
-
-    protected int maxFilterQueriesForRole = 3;
-
-    protected int timeAllowed = -1;
-
-    protected Map<String, String[]> requestParameterMap = new HashMap<String, String[]>();
-
-    protected String additionalGeoQuery;
-
-    protected Map<String, String> fieldLanguageMap = new HashMap<String, String>();
-
-    protected int maxSearchResultOffset = 100000;
-
-    protected List<SortField> defaultSortFieldList = new ArrayList<SortField>();
-
-    protected String highlightingPrefix = "hl_";
-
-    protected String minimumShouldMatch = "100%";
-
-    protected FacetInfo defaultFacetInfo;
-
-    protected MoreLikeThisInfo defaultMoreLikeThisInfo;
-
-    protected GeoInfo defaultGeoInfo;
-
-    protected String defaultQueryLanguage;
-
-    protected Map<String, String[]> additionalQueryParamMap = new HashMap<String, String[]>();
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see jp.sf.fess.helper.QueryHelper#build(java.lang.String)
-     */
-    @Override
-    public SearchQuery build(final String query, final boolean envCondition) {
-        String q;
-        if (envCondition && additionalQuery != null
-                && StringUtil.isNotBlank(query)) {
-            q = query + " " + additionalQuery;
-        } else {
-            q = query;
-        }
-
-        final SearchQuery searchQuery = buildQuery(q);
-        if (!searchQuery.queryExists()) {
-            return searchQuery.query(StringUtil.EMPTY);
-        }
-
-        if (browserTypeHelper == null && roleQueryHelper == null
-                || !envCondition) {
-            return searchQuery;
-        }
-
-        StringBuilder queryBuf = new StringBuilder(255);
-        queryBuf.append(searchQuery.getQuery());
-
-        if (browserTypeHelper != null) {
-            searchQuery
-                    .addFilterQuery("type:"
-                            + QueryUtil.escapeValue(browserTypeHelper
-                                    .getBrowserType()));
-        }
-
-        if (roleQueryHelper != null) {
-            final Set<String> roleSet = roleQueryHelper.build();
-            if (roleSet.size() > maxFilterQueriesForRole) {
-                // add query
-                final String sq = queryBuf.toString();
-                queryBuf = new StringBuilder(255);
-                final boolean hasQueries = sq.contains(_AND_)
-                        || sq.contains(_OR_);
-                if (hasQueries) {
-                    queryBuf.append('(');
-                }
-                queryBuf.append(sq);
-                if (hasQueries) {
-                    queryBuf.append(')');
-                }
-                queryBuf.append(_AND_);
-                if (roleSet.size() > 1) {
-                    queryBuf.append('(');
-                }
-                queryBuf.append(getRoleQuery(roleSet));
-                if (roleSet.size() > 1) {
-                    queryBuf.append(')');
-                }
-            } else if (!roleSet.isEmpty()) {
-                // add filter query
-                searchQuery.addFilterQuery(getRoleQuery(roleSet));
-            }
-        }
-
-        return searchQuery.query(queryBuf.toString());
-    }
-
-    private String getRoleQuery(final Set<String> roleList) {
-        final StringBuilder queryBuf = new StringBuilder(255);
-        boolean isFirst = true;
-        for (final String role : roleList) {
-            if (isFirst) {
-                isFirst = false;
-            } else {
-                queryBuf.append(_OR_);
-
-            }
-            queryBuf.append("role:");
-            queryBuf.append(QueryUtil.escapeValue(role));
-        }
-        return queryBuf.toString();
-    }
-
-    protected SearchQuery buildQuery(final String query) {
-        final Map<String, String> sortFieldMap = new LinkedHashMap<String, String>();
-        final List<String> highLightQueryList = new ArrayList<String>();
-        final Map<String, List<String>> fieldLogMap = new HashMap<String, List<String>>();
-        final SearchQuery searchQuery = new SearchQuery();
-
-        final String q = buildQuery(query, sortFieldMap, highLightQueryList,
-                fieldLogMap);
-        String solrQuery;
-        if (q == null || "()".equals(q)) {
-            solrQuery = StringUtil.EMPTY;
-            // } else if (q.startsWith("(") && q.endsWith(")")) {
-            // solrQuery = q.substring(1, q.length() - 1);
-        } else {
-            solrQuery = unbracketQuery(q);
-        }
-        searchQuery.setQuery(solrQuery);
-
-        searchQuery.setMinimumShouldMatch(minimumShouldMatch);
-
-        for (final Map.Entry<String, String> entry : sortFieldMap.entrySet()) {
-            searchQuery.addSortField(entry.getKey(), entry.getValue());
-        }
-        // set queries to request for HighLight
-        final HttpServletRequest request = RequestUtil.getRequest();
-        if (request != null) {
-            request.setAttribute(Constants.HIGHLIGHT_QUERIES,
-                    highLightQueryList.toArray(new String[highLightQueryList
-                            .size()]));
-            request.setAttribute(Constants.FIELD_LOGS, fieldLogMap);
-        }
-        return searchQuery;
-    }
-
-    protected String unbracketQuery(final String query) {
-        if (query.startsWith("(") && query.endsWith(")")) {
-            int count = 0;
-            int depth = 0;
-            int escape = 0;
-            for (int i = 0; i < query.length(); i++) {
-                final char c = query.charAt(i);
-                if (c == '\\') {
-                    escape++;
-                } else {
-                    if (c == '(' && escape % 2 == 0) {
-                        if (depth == 0) {
-                            count++;
-                        }
-                        depth++;
-                    } else if (c == ')' && escape % 2 == 0) {
-                        depth--;
-                    }
-                    escape = 0;
-                }
-            }
-            if (depth == 0 && count == 1) {
-                return unbracketQuery(query.substring(1, query.length() - 1));
-            }
-        }
-        return query;
-
-    }
-
-    protected String buildQuery(final String query,
-            final Map<String, String> sortFieldMap,
-            final List<String> highLightQueryList,
-            final Map<String, List<String>> fieldLogMap) {
-        final List<QueryPart> queryPartList = splitQuery(query, sortFieldMap,
-                highLightQueryList, fieldLogMap);
-        if (queryPartList == null || queryPartList.isEmpty()) {
-            return null;
-        }
-
-        final StringBuilder queryBuf = new StringBuilder(255);
-        final List<String> notOperatorList = new ArrayList<String>();
-        String operator = _AND_;
-        boolean notOperatorFlag = false;
-        int queryOperandCount = 0;
-        int contentOperandCount = 0;
-        final String queryLanguage = getQueryLanguage();
-        for (final QueryPart queryPart : queryPartList) {
-            if (queryPart.isParsed()) {
-                if (queryBuf.length() > 0) {
-                    queryBuf.append(operator);
-                }
-                queryBuf.append(queryPart.getValue());
-                continue;
-            }
-            final String value = queryPart.getValue();
-            boolean nonPrefix = false;
-            // check prefix
-            for (final String field : searchFields) {
-                String prefix = field + ":";
-                if (value.startsWith(prefix)
-                        && value.length() != prefix.length()) {
-                    if (queryBuf.length() > 0 && !notOperatorFlag) {
-                        queryBuf.append(operator);
-                    }
-                    boolean isInUrl = false;
-                    final String targetWord = value.substring(prefix.length());
-                    if (INURL_FIELD.equals(field)) {
-                        prefix = "url:";
-                        isInUrl = true;
-                    }
-                    String fieldLogWord;
-                    if (notOperatorFlag) {
-                        final StringBuilder buf = new StringBuilder(100);
-                        buf.append(prefix);
-                        if (isInUrl) {
-                            buf.append('*');
-                        }
-                        appendQueryValue(buf, targetWord);
-                        if (isInUrl) {
-                            buf.append('*');
-                        }
-                        notOperatorList.add(buf.toString());
-                        notOperatorFlag = false;
-                        fieldLogWord = NOT_ + targetWord;
-                    } else {
-                        queryBuf.append(prefix);
-                        if (isInUrl) {
-                            queryBuf.append('*');
-                        }
-                        appendQueryValue(queryBuf, targetWord);
-                        if (isInUrl) {
-                            queryBuf.append('*');
-                        }
-                        queryOperandCount++;
-                        fieldLogWord = targetWord;
-                    }
-                    nonPrefix = true;
-                    operator = _AND_;
-                    if (!LABEL_FIELD.equals(field)) {
-                        highLightQueryList.add(targetWord);
-                    }
-                    if (fieldLogMap != null) {
-                        addFieldLogValue(fieldLogMap, field, fieldLogWord);
-                    }
-                    break;
-                }
-            }
-
-            // sort
-            if (value.startsWith(sortPrefix)
-                    && value.length() != sortPrefix.length()) {
-                final String[] sortFieldPairs = value.substring(
-                        sortPrefix.length()).split(",");
-                for (final String sortFieldPairStr : sortFieldPairs) {
-                    final String[] sortFieldPair = sortFieldPairStr
-                            .split("\\.");
-                    if (isSupportedSortField(sortFieldPair[0])) {
-                        if (sortFieldPair.length == 1) {
-                            sortFieldMap.put(sortFieldPair[0], Constants.ASC);
-                        } else {
-                            sortFieldMap
-                                    .put(sortFieldPair[0], sortFieldPair[1]);
-                        }
-                    }
-                }
-                continue;
-            }
-
-            if (!nonPrefix) {
-                if (AND.equals(value)) {
-                    operator = _AND_;
-                } else if (OR.equals(value)) {
-                    operator = _OR_;
-                } else if (NOT.equals(value)) {
-                    notOperatorFlag = true;
-                } else if (notOperatorFlag) {
-                    final StringBuilder buf = new StringBuilder(100);
-
-                    buildContentQueryWithLang(buf, value, queryLanguage);
-                    notOperatorList.add(buf.toString());
-
-                    operator = _AND_;
-                    notOperatorFlag = false;
-                    highLightQueryList.add(value);
-
-                    if (fieldLogMap != null) {
-                        addFieldLogValue(fieldLogMap, CONTENT_FIELD, NOT_
-                                + value);
-                    }
-                } else {
-                    // content
-                    if (queryBuf.length() > 0) {
-                        queryBuf.append(operator);
-                    }
-                    buildContentQueryWithLang(queryBuf, value, queryLanguage);
-                    contentOperandCount++;
-
-                    operator = _AND_;
-                    highLightQueryList.add(value);
-
-                    if (fieldLogMap != null) {
-                        addFieldLogValue(fieldLogMap, CONTENT_FIELD, value);
-                    }
-                }
-            }
-        }
-
-        StringBuilder searchQueryBuf = new StringBuilder(255);
-        if (queryBuf.length() > 0) {
-            searchQueryBuf.append(queryBuf.toString());
-            operator = _AND_;
-        } else {
-            operator = StringUtil.EMPTY;
-        }
-        if (!notOperatorList.isEmpty()) {
-            final String q = searchQueryBuf.toString();
-            searchQueryBuf = new StringBuilder(255);
-            final int count = queryOperandCount + contentOperandCount;
-            if (count > 1) {
-                searchQueryBuf.append('(');
-            }
-            searchQueryBuf.append(q);
-            if (count > 1) {
-                searchQueryBuf.append(')');
-            }
-            for (final String notOperator : notOperatorList) {
-                searchQueryBuf.append(operator);
-                searchQueryBuf.append(NOT_);
-                searchQueryBuf.append(notOperator);
-                operator = _AND_;
-            }
-        }
-
-        return searchQueryBuf.toString();
-    }
-
-    private void addFieldLogValue(final Map<String, List<String>> fieldLogMap,
-            final String field, final String targetWord) {
-        List<String> logList = fieldLogMap.get(field);
-        if (logList == null) {
-            logList = new ArrayList<String>();
-            fieldLogMap.put(field, logList);
-        }
-        logList.add(targetWord);
-    }
-
-    protected void appendQueryValue(final StringBuilder buf, final String query) {
-        // check reserved
-        boolean reserved = false;
-        for (final String element : Constants.RESERVED) {
-            if (element.equals(query)) {
-                reserved = true;
-                break;
-            }
-        }
-
-        if (reserved) {
-            buf.append('\\');
-            buf.append(query);
-            return;
-        }
-
-        String value = query;
-        if (useBigram && value.length() == 1
-                && !StringUtils.isAsciiPrintable(value)) {
-            // if using bigram, add ?
-            value = value + '?';
-        }
-
-        String fuzzyValue = null;
-        String proximityValue = null;
-        String caretValue = null;
-        final int tildePos = value.lastIndexOf('~');
-        final int caretPos = value.indexOf('^');
-        if (tildePos > caretPos) {
-            if (tildePos > 0) {
-                final String tildeValue = value.substring(tildePos);
-                if (tildeValue.length() > 1) {
-                    final StringBuilder buf1 = new StringBuilder();
-                    final StringBuilder buf2 = new StringBuilder();
-                    boolean isComma = false;
-                    for (int i = 1; i < tildeValue.length(); i++) {
-                        final char c = tildeValue.charAt(i);
-                        if (c >= '0' && c <= '9') {
-                            if (isComma) {
-                                buf2.append(c);
-                            } else {
-                                buf1.append(c);
-                            }
-                        } else if (c == '.') {
-                            if (isComma) {
-                                break;
-                            } else {
-                                isComma = true;
-                            }
-                        } else {
-                            break;
-                        }
-                    }
-                    if (buf1.length() == 0) {
-                        fuzzyValue = "~";
-                    } else {
-                        final int intValue = Integer.parseInt(buf1.toString());
-                        if (intValue <= 0) {
-                            // fuzzy
-                            buf1.append('.').append(buf2.toString());
-                            fuzzyValue = '~' + buf1.toString();
-                        } else {
-                            // proximity
-                            proximityValue = '~' + Integer.toString(intValue);
-                        }
-                    }
-                } else {
-                    fuzzyValue = "~";
-                }
-
-                value = value.substring(0, tildePos);
-            }
-        } else {
-            if (caretPos > 0) {
-                caretValue = value.substring(caretPos);
-                value = value.substring(0, caretPos);
-            }
-        }
-        if (value.startsWith("[") && value.endsWith("]")) {
-            appendRangeQueryValue(buf, value, '[', ']');
-        } else if (value.startsWith("{") && value.endsWith("}")) {
-            // TODO function
-            appendRangeQueryValue(buf, value, '{', '}');
-        } else {
-            if (proximityValue == null) {
-                buf.append(QueryUtil.escapeValue(value));
-            } else {
-                buf.append('"').append(QueryUtil.escapeValue(value))
-                        .append('"');
-            }
-        }
-
-        if (fuzzyValue != null) {
-            buf.append(fuzzyValue);
-        } else if (proximityValue != null) {
-            buf.append(proximityValue);
-        } else if (caretValue != null) {
-            buf.append(caretValue);
-        }
-    }
-
-    protected void appendRangeQueryValue(final StringBuilder buf,
-            final String value, final char prefix, final char suffix) {
-        final String[] split = value.substring(1, value.length() - 1).split(
-                _TO_);
-        if (split.length == 2 && split[0].length() > 0 && split[1].length() > 0) {
-            final String value1 = split[0].trim();
-            final String value2 = split[1].trim();
-            if ("*".equals(value1) && "*".equals(value2)) {
-                throw new InvalidQueryException(
-                        "errors.invalid_query_str_range", "Invalid range: "
-                                + value);
-            }
-            buf.append(prefix);
-            buf.append(QueryUtil.escapeRangeValue(value1));
-            buf.append(_TO_);
-            buf.append(QueryUtil.escapeRangeValue(value2));
-            buf.append(suffix);
-        } else {
-            throw new InvalidQueryException("errors.invalid_query_str_range",
-                    "Invalid range: " + value);
-        }
-    }
-
-    private boolean isSupportedSortField(final String field) {
-        for (final String f : supportedSortFields) {
-            if (f.equals(field)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    protected List<QueryPart> splitQuery(final String query,
-            final Map<String, String> sortFieldMap,
-            final List<String> highLightQueryList,
-            final Map<String, List<String>> fieldLogMap) {
-        final List<QueryPart> valueList = new ArrayList<QueryPart>();
-        StringBuilder buf = new StringBuilder();
-        boolean quoted = false;
-        int parenthesis = 0;
-        int squareBracket = 0;
-        int curlyBracket = 0;
-        char oldChar = 0;
-        for (int i = 0; i < query.length(); i++) {
-            final char c = query.charAt(i);
-            if (oldChar == '\\'
-                    && (c == '"' || c == '(' || c == '{' || c == '[')) {
-                buf.append(c);
-            } else {
-                if (oldChar == '\\') {
-                    buf.append('\\');
-                }
-                switch (c) {
-                case '(':
-                    buf.append(c);
-                    if (!quoted && squareBracket == 0 && curlyBracket == 0) {
-                        parenthesis++;
-                    }
-                    break;
-                case ')':
-                    buf.append(c);
-                    if (!quoted && squareBracket == 0 && curlyBracket == 0) {
-                        parenthesis--;
-                    }
-                    break;
-                case '[':
-                    buf.append(c);
-                    if (!quoted && parenthesis == 0 && curlyBracket == 0) {
-                        squareBracket++;
-                    }
-                    break;
-                case ']':
-                    buf.append(c);
-                    if (!quoted && parenthesis == 0 && curlyBracket == 0) {
-                        squareBracket--;
-                    }
-                    break;
-                case '{':
-                    buf.append(c);
-                    if (!quoted && parenthesis == 0 && squareBracket == 0) {
-                        curlyBracket++;
-                    }
-                    break;
-                case '}':
-                    buf.append(c);
-                    if (!quoted && parenthesis == 0 && squareBracket == 0) {
-                        curlyBracket--;
-                    }
-                    break;
-                case '"':
-                    if (parenthesis == 0 && curlyBracket == 0
-                            && squareBracket == 0) {
-                        quoted ^= true;
-                    } else {
-                        buf.append(c);
-                    }
-                    break;
-                case '\\':
-                    break;
-                case ' ':
-                case '\u3000':
-                    if (quoted || curlyBracket > 0 || squareBracket > 0
-                            || parenthesis > 0) {
-                        buf.append(c);
-                    } else {
-                        if (buf.length() > 0) {
-                            addQueryPart(buf.toString(), valueList,
-                                    sortFieldMap, highLightQueryList,
-                                    fieldLogMap);
-                        }
-                        buf = new StringBuilder();
-                    }
-                    break;
-                default:
-                    buf.append(c);
-                    break;
-                }
-            }
-            oldChar = c;
-        }
-        if (oldChar == '\\') {
-            buf.append('\\');
-        }
-        if (quoted) {
-            throw new InvalidQueryException("errors.invalid_query_quoted",
-                    "Invalid quoted: " + query);
-        } else if (curlyBracket > 0) {
-            throw new InvalidQueryException(
-                    "errors.invalid_query_curly_bracket",
-                    "Invalid curly bracket: " + query);
-        } else if (squareBracket > 0) {
-            throw new InvalidQueryException(
-                    "errors.invalid_query_square_bracket",
-                    "Invalid square bracket: " + query);
-        } else if (parenthesis > 0) {
-            throw new InvalidQueryException("errors.invalid_query_parenthesis",
-                    "Invalid parenthesis: " + query);
-        }
-        if (buf.length() > 0) {
-            addQueryPart(buf.toString(), valueList, sortFieldMap,
-                    highLightQueryList, fieldLogMap);
-        }
-        return valueList;
-    }
-
-    private void addQueryPart(final String str,
-            final List<QueryPart> valueList,
-            final Map<String, String> sortFieldMap,
-            final List<String> highLightQueryList,
-            final Map<String, List<String>> fieldLogMap) {
-        if (str.startsWith("[") || str.startsWith("{")) {
-            valueList.add(new QueryPart(str.trim()));
-        } else if (str.startsWith("(") && str.endsWith(")") && str.length() > 2) {
-            final String q = str.substring(1, str.length() - 1);
-            if (sortFieldMap != null && highLightQueryList != null) {
-                final String innerQuery = buildQuery(q, sortFieldMap,
-                        highLightQueryList, fieldLogMap);
-                if (StringUtil.isNotBlank(innerQuery)) {
-                    valueList.add(new QueryPart("(" + innerQuery + ")", true));
-                }
-            } else {
-                // facet query
-                final String innerQuery = buildFacetQuery(q);
-                if (StringUtil.isNotBlank(innerQuery)) {
-                    valueList.add(new QueryPart("(" + innerQuery + ")", true));
-                }
-            }
-        } else {
-            valueList.add(new QueryPart(str.trim()));
-        }
-    }
-
-    @Override
-    public boolean isFacetField(final String field) {
-        if (StringUtil.isBlank(field)) {
-            return false;
-        }
-        boolean flag = false;
-        for (final String f : facetFields) {
-            if (field.equals(f)) {
-                flag = true;
-            }
-        }
-        return flag;
-    }
-
-    @Override
-    public String buildFacetQuery(final String query) {
-        final String q = buildFacetQueryInternal(query);
-        String solrQuery;
-        if (q == null || "()".equals(q)) {
-            solrQuery = StringUtil.EMPTY;
-        } else {
-            solrQuery = unbracketQuery(q);
-        }
-        return solrQuery;
-    }
-
-    protected String buildFacetQueryInternal(final String query) {
-        final List<QueryPart> queryPartList = splitQuery(query, null, null,
-                null);
-        if (queryPartList.isEmpty()) {
-            return StringUtil.EMPTY;
-        }
-
-        final StringBuilder queryBuf = new StringBuilder(255);
-        final List<String> notOperatorList = new ArrayList<String>();
-        String operator = _AND_;
-        boolean notOperatorFlag = false;
-        int queryOperandCount = 0;
-        int contentOperandCount = 0;
-        final String queryLanguage = getQueryLanguage();
-        for (final QueryPart queryPart : queryPartList) {
-            if (queryPart.isParsed()) {
-                if (queryBuf.length() > 0) {
-                    queryBuf.append(operator);
-                }
-                queryBuf.append(queryPart.getValue());
-                continue;
-            }
-            final String value = queryPart.getValue();
-            boolean nonPrefix = false;
-            // check prefix
-            for (final String field : facetFields) {
-                final String prefix = field + ":";
-                if (value.startsWith(prefix)
-                        && value.length() != prefix.length()) {
-                    if (queryBuf.length() > 0) {
-                        queryBuf.append(operator);
-                    }
-                    final String targetWord = value.substring(prefix.length());
-                    if (notOperatorFlag) {
-                        final StringBuilder buf = new StringBuilder(100);
-                        buf.append(prefix);
-                        appendQueryValue(buf, targetWord);
-                        notOperatorList.add(buf.toString());
-                        notOperatorFlag = false;
-                    } else {
-                        queryBuf.append(prefix);
-                        appendQueryValue(queryBuf, targetWord);
-                        queryOperandCount++;
-                    }
-                    nonPrefix = true;
-                    operator = _AND_;
-                    break;
-                }
-            }
-
-            // sort
-            if (value.startsWith(sortPrefix)
-                    && value.length() != sortPrefix.length()) {
-                // skip
-                continue;
-            }
-
-            if (!nonPrefix) {
-                if (AND.equals(value)) {
-                    operator = _AND_;
-                } else if (OR.equals(value)) {
-                    operator = _OR_;
-                } else if (NOT.equals(value)) {
-                    notOperatorFlag = true;
-                } else if (notOperatorFlag) {
-                    final StringBuilder buf = new StringBuilder(100);
-
-                    buildContentQueryWithLang(buf, value, queryLanguage);
-                    notOperatorList.add(buf.toString());
-
-                    operator = _AND_;
-                    notOperatorFlag = false;
-                } else {
-                    // content
-                    if (queryBuf.length() > 0) {
-                        queryBuf.append(operator);
-                    }
-                    buildContentQueryWithLang(queryBuf, value, queryLanguage);
-                    contentOperandCount++;
-
-                    operator = _AND_;
-                }
-            }
-        }
-
-        StringBuilder searchQueryBuf = new StringBuilder(255);
-        if (queryBuf.length() > 0) {
-            searchQueryBuf.append(queryBuf.toString());
-            operator = _AND_;
-        } else {
-            operator = StringUtil.EMPTY;
-        }
-        if (!notOperatorList.isEmpty()) {
-            final String q = searchQueryBuf.toString();
-            searchQueryBuf = new StringBuilder(255);
-            final int count = queryOperandCount + contentOperandCount;
-            if (count > 1) {
-                searchQueryBuf.append('(');
-            }
-            searchQueryBuf.append(q);
-            if (count > 1) {
-                searchQueryBuf.append(')');
-            }
-            for (final String notOperator : notOperatorList) {
-                searchQueryBuf.append(operator);
-                searchQueryBuf.append(NOT_);
-                searchQueryBuf.append(notOperator);
-                operator = _AND_;
-            }
-        }
-
-        return searchQueryBuf.toString();
-    }
-
-    protected void buildContentQueryWithLang(final StringBuilder buf,
-            final String value, final String queryLanguage) {
-        buf.append('(');
-        buf.append(TITLE_FIELD).append(':');
-        appendQueryValue(buf, value);
-        buf.append(_OR_);
-        buf.append(CONTENT_FIELD).append(':');
-        appendQueryValue(buf, value);
-        if (StringUtil.isNotBlank(queryLanguage)) {
-            buf.append(_OR_);
-            buf.append("content_");
-            buf.append(queryLanguage);
-            buf.append(':');
-            appendQueryValue(buf, value);
-        }
-        buf.append(')');
-    }
-
-    protected String getQueryLanguage() {
-        final String[] supportedLanguages = systemHelper
-                .getSupportedLanguages();
-        if (supportedLanguages.length == 0) {
-            return null;
-        }
-        if (defaultQueryLanguage != null) {
-            return defaultQueryLanguage;
-        }
-        final HttpServletRequest request = RequestUtil.getRequest();
-        if (request == null) {
-            return null;
-        }
-        final Locale locale = request.getLocale();
-        if (locale == null) {
-            return null;
-        }
-        final String language = locale.getLanguage();
-        final String country = locale.getCountry();
-        if (StringUtil.isNotBlank(language)) {
-            if (StringUtil.isNotBlank(country)) {
-                final String lang = language + "_" + country;
-                for (final String value : supportedLanguages) {
-                    if (value.equals(lang)) {
-                        final String fieldLang = fieldLanguageMap.get(value);
-                        if (fieldLang == null) {
-                            return value;
-                        } else {
-                            return fieldLang;
-                        }
-                    }
-                }
-            }
-            for (final String value : supportedLanguages) {
-                if (value.equals(language)) {
-                    final String fieldLang = fieldLanguageMap.get(value);
-                    if (fieldLang == null) {
-                        return value;
-                    } else {
-                        return fieldLang;
-                    }
-                }
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public boolean isFacetSortValue(final String sort) {
-        return "count".equals(sort) || "index".equals(sort);
-    }
-
-    @Override
-    public String buildOptionQuery(final SearchParamMap optionMap) {
-        if (optionMap == null) {
-            return StringUtil.EMPTY;
-        }
-
-        final StringBuilder buf = new StringBuilder();
-
-        final String[] qs = optionMap.get(Constants.OPTION_QUERY_Q);
-        if (qs != null) {
-            for (final String q : qs) {
-                if (StringUtil.isNotBlank(q)) {
-                    buf.append(' ');
-                    buf.append(q);
-                }
-            }
-        }
-
-        final String[] cqs = optionMap.get(Constants.OPTION_QUERY_CQ);
-        if (cqs != null) {
-            for (final String cq : cqs) {
-                if (StringUtil.isNotBlank(cq)) {
-                    buf.append(' ');
-                    char split = 0;
-                    final List<QueryPart> partList = splitQuery(
-                            cq.indexOf('"') >= 0 ? cq : "\"" + cq + "\"", null,
-                            null, null);
-                    for (final QueryPart part : partList) {
-                        if (split == 0) {
-                            split = ' ';
-                        } else {
-                            buf.append(split);
-                        }
-                        final String value = part.getValue();
-                        buf.append('"');
-                        buf.append(value);
-                        buf.append('"');
-                    }
-                }
-            }
-        }
-
-        final String[] oqs = optionMap.get(Constants.OPTION_QUERY_OQ);
-        if (oqs != null) {
-            for (final String oq : oqs) {
-                if (StringUtil.isNotBlank(oq)) {
-                    buf.append(' ');
-                    final List<QueryPart> partList = splitQuery(oq, null, null,
-                            null);
-                    final boolean append = partList.size() > 1
-                            && optionMap.size() > 1;
-                    if (append) {
-                        buf.append('(');
-                    }
-                    String split = null;
-                    for (final QueryPart part : partList) {
-                        if (split == null) {
-                            split = _OR_;
-                        } else {
-                            buf.append(split);
-                        }
-                        final String value = part.getValue();
-                        final boolean hasSpace = value.matches(".*\\s.*");
-                        if (hasSpace) {
-                            buf.append('"');
-                        }
-                        buf.append(value);
-                        if (hasSpace) {
-                            buf.append('"');
-                        }
-                    }
-                    if (append) {
-                        buf.append(')');
-                    }
-                }
-            }
-        }
-
-        final String[] nqs = optionMap.get(Constants.OPTION_QUERY_NQ);
-        if (nqs != null) {
-            for (final String nq : nqs) {
-                if (StringUtil.isNotBlank(nq)) {
-                    buf.append(' ');
-                    String split = StringUtil.EMPTY;
-                    final List<QueryPart> partList = splitQuery(nq, null, null,
-                            null);
-                    for (final QueryPart part : partList) {
-                        buf.append(split);
-                        if (split.length() == 0) {
-                            split = " ";
-                        }
-                        buf.append(NOT_);
-                        final String value = part.getValue();
-                        final boolean hasSpace = value.matches(".*\\s.*");
-                        if (hasSpace) {
-                            buf.append('"');
-                        }
-                        buf.append(value);
-                        if (hasSpace) {
-                            buf.append('"');
-                        }
-                    }
-                }
-            }
-        }
-
-        return buf.toString().trim();
-    }
-
-    public void setApiResponseFields(final String[] fields) {
-        apiResponseFieldSet = new HashSet<String>();
-        for (final String field : fields) {
-            apiResponseFieldSet.add(field);
-        }
-    }
-
-    @Override
-    public boolean isApiResponseField(final String field) {
-        if (apiResponseFieldSet == null) {
-            return true;
-        }
-        return apiResponseFieldSet.contains(field);
-    }
-
-    /**
-     * @return the responseFields
-     */
-    @Override
-    public String[] getResponseFields() {
-        return responseFields;
-    }
-
-    /**
-     * @param responseFields the responseFields to set
-     */
-    public void setResponseFields(final String[] responseFields) {
-        this.responseFields = responseFields;
-    }
-
-    @Override
-    public String[] getResponseDocValuesFields() {
-        return responseDocValuesFields;
-    }
-
-    public void setResponseDocValuesFields(
-            final String[] responseDocValuesFields) {
-        this.responseDocValuesFields = responseDocValuesFields;
-    }
-
-    /**
-     * @return the highlightingFields
-     */
-    @Override
-    public String[] getHighlightingFields() {
-        return highlightingFields;
-    }
-
-    /**
-     * @param highlightingFields the highlightingFields to set
-     */
-    public void setHighlightingFields(final String[] highlightingFields) {
-        this.highlightingFields = highlightingFields;
-    }
-
-    /**
-     * @return the supportedFields
-     */
-    public String[] getSearchFields() {
-        return searchFields;
-    }
-
-    /**
-     * @param supportedFields the supportedFields to set
-     */
-    public void setSearchFields(final String[] supportedFields) {
-        searchFields = supportedFields;
-    }
-
-    /**
-     * @return the facetFields
-     */
-    public String[] getFacetFields() {
-        return facetFields;
-    }
-
-    /**
-     * @param facetFields the facetFields to set
-     */
-    public void setFacetFields(final String[] facetFields) {
-        this.facetFields = facetFields;
-    }
-
-    /**
-     * @return the sortPrefix
-     */
-    public String getSortPrefix() {
-        return sortPrefix;
-    }
-
-    /**
-     * @param sortPrefix the sortPrefix to set
-     */
-    public void setSortPrefix(final String sortPrefix) {
-        this.sortPrefix = sortPrefix;
-    }
-
-    /**
-     * @return the supportedSortFields
-     */
-    public String[] getSupportedSortFields() {
-        return supportedSortFields;
-    }
-
-    /**
-     * @param supportedSortFields the supportedSortFields to set
-     */
-    public void setSupportedSortFields(final String[] supportedSortFields) {
-        this.supportedSortFields = supportedSortFields;
-    }
-
-    /**
-     * @return the highlightSnippetSize
-     */
-    @Override
-    public int getHighlightSnippetSize() {
-        return highlightSnippetSize;
-    }
-
-    /**
-     * @param highlightSnippetSize the highlightSnippetSize to set
-     */
-    public void setHighlightSnippetSize(final int highlightSnippetSize) {
-        this.highlightSnippetSize = highlightSnippetSize;
-    }
-
-    /**
-     * @return the shards
-     */
-    @Override
-    public String getShards() {
-        return shards;
-    }
-
-    /**
-     * @param shards the shards to set
-     */
-    public void setShards(final String shards) {
-        this.shards = shards;
-    }
-
-    /**
-     * @return the useBigram
-     */
-    public boolean isUseBigram() {
-        return useBigram;
-    }
-
-    /**
-     * @param useBigram the useBigram to set
-     */
-    public void setUseBigram(final boolean useBigram) {
-        this.useBigram = useBigram;
-    }
-
-    /**
-     * @return the additionalQuery
-     */
-    public String getAdditionalQuery() {
-        return additionalQuery;
-    }
-
-    /**
-     * @param additionalQuery the additionalQuery to set
-     */
-    public void setAdditionalQuery(final String additionalQuery) {
-        this.additionalQuery = additionalQuery;
-    }
-
-    public int getMaxFilterQueriesForRole() {
-        return maxFilterQueriesForRole;
-    }
-
-    public void setMaxFilterQueriesForRole(final int maxFilterQuerysForRole) {
-        maxFilterQueriesForRole = maxFilterQuerysForRole;
-    }
-
-    /**
-     * @return the timeAllowed
-     */
-    @Override
-    public int getTimeAllowed() {
-        return timeAllowed;
-    }
-
-    /**
-     * @param timeAllowed the timeAllowed to set
-     */
-    public void setTimeAllowed(final int timeAllowed) {
-        this.timeAllowed = timeAllowed;
-    }
-
-    public void addRequestParameter(final String name, final String... values) {
-        requestParameterMap.put(name, values);
-    }
-
-    public void addRequestParameter(final String name, final String value) {
-        if (value != null) {
-            requestParameterMap.put(name, new String[] { value });
-        }
-    }
-
-    @Override
-    public Set<Entry<String, String[]>> getRequestParameterSet() {
-        return requestParameterMap.entrySet();
-    }
-
-    @Override
-    public String getAdditionalGeoQuery() {
-        return additionalGeoQuery;
-    }
-
-    public void setAdditionalGeoQuery(final String additionalGeoQuery) {
-        this.additionalGeoQuery = additionalGeoQuery;
-    }
-
-    public void addFieldLanguage(final String lang, final String fieldLang) {
-        fieldLanguageMap.put(lang, fieldLang);
-    }
-
-    @Override
-    public int getMaxSearchResultOffset() {
-        return maxSearchResultOffset;
-    }
-
-    public void setMaxSearchResultOffset(final int maxSearchResultOffset) {
-        this.maxSearchResultOffset = maxSearchResultOffset;
-    }
-
-    public void addDefaultSortField(final String fieldName, final String order) {
-        final SortField sortField = new SortField();
-        sortField.setField(fieldName);
-        sortField
-                .setOrder(Constants.ASC.equalsIgnoreCase(order) ? Constants.ASC
-                        : Constants.DESC);
-        defaultSortFieldList.add(sortField);
-    }
-
-    @Override
-    public boolean hasDefaultSortFields() {
-        return !defaultSortFieldList.isEmpty();
-    }
-
-    @Override
-    public SortField[] getDefaultSortFields() {
-        return defaultSortFieldList.toArray(new SortField[defaultSortFieldList
-                .size()]);
-    }
-
-    public void setHighlightingPrefix(final String highlightingPrefix) {
-        this.highlightingPrefix = highlightingPrefix;
-    }
-
-    @Override
-    public String getHighlightingPrefix() {
-        return highlightingPrefix;
-    }
-
-    public String[] getSupportedMltFields() {
-        return supportedMltFields;
-    }
-
-    public void setSupportedMltFields(final String[] supportedMltFields) {
-        this.supportedMltFields = supportedMltFields;
-    }
-
-    @Override
-    public String getMoreLikeThisField(final String[] fields) {
-        if (fields == null || fields.length == 0) {
-            return null;
-        }
-        final List<String> list = new ArrayList<String>();
-        for (final String field : fields) {
-            if (StringUtil.isNotBlank(field)) {
-                for (final String f : field.split(",")) {
-                    final String value = f.trim();
-                    for (final String supported : supportedMltFields) {
-                        if (supported.equals(value)) {
-                            list.add(value);
-                        }
-                    }
-                }
-            }
-        }
-        if (list.isEmpty()) {
-            return null;
-        }
-        return StringUtils.join(list, ',');
-    }
-
-    public String getMinimumShouldMatch() {
-        return minimumShouldMatch;
-    }
-
-    public void setMinimumShouldMatch(final String minimumShouldMatch) {
-        this.minimumShouldMatch = minimumShouldMatch;
-    }
-
-    @Override
-    public boolean isAnalysisFieldName(final String fieldName) {
-        for (final String f : supportedAnalysisFields) {
-            if (f.endsWith(fieldName)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public FacetInfo getDefaultFacetInfo() {
-        return defaultFacetInfo;
-    }
-
-    public void setDefaultFacetInfo(final FacetInfo defaultFacetInfo) {
-        this.defaultFacetInfo = defaultFacetInfo;
-    }
-
-    @Override
-    public MoreLikeThisInfo getDefaultMoreLikeThisInfo() {
-        return defaultMoreLikeThisInfo;
-    }
-
-    public void setDefaultMoreLikeThisInfo(
-            final MoreLikeThisInfo defaultMoreLikeThisInfo) {
-        this.defaultMoreLikeThisInfo = defaultMoreLikeThisInfo;
-    }
-
-    @Override
-    public GeoInfo getDefaultGeoInfo() {
-        return defaultGeoInfo;
-    }
-
-    public void setDefaultGeoInfo(final GeoInfo defaultGeoInfo) {
-        this.defaultGeoInfo = defaultGeoInfo;
-    }
-
-    public String getDefaultQueryLanguage() {
-        return defaultQueryLanguage;
-    }
-
-    public void setDefaultQueryLanguage(final String defaultQueryLanguage) {
-        this.defaultQueryLanguage = defaultQueryLanguage;
-    }
-
-    @Override
-    public Map<String, String[]> getQueryParamMap() {
-        if (additionalQueryParamMap.isEmpty()) {
-            return additionalQueryParamMap;
-        }
-
-        final HttpServletRequest request = RequestUtil.getRequest();
-        final Map<String, String[]> queryParamMap = new HashMap<String, String[]>();
-        for (final Map.Entry<String, String[]> entry : additionalQueryParamMap
-                .entrySet()) {
-            final String[] values = entry.getValue();
-            final String[] newValues = new String[values.length];
-            for (int i = 0; i < values.length; i++) {
-                final String value = values[i];
-                if (value.length() > 1 && value.charAt(0) == '$'
-                        && request != null) {
-                    final String param = request.getParameter(value
-                            .substring(1));
-                    if (StringUtil.isNotBlank(param)) {
-                        newValues[i] = param;
-                    } else {
-                        newValues[i] = StringUtil.EMPTY;
-                    }
-                } else {
-                    newValues[i] = value;
-                }
-            }
-            queryParamMap.put(entry.getKey(), newValues);
-        }
-
-        return queryParamMap;
-    }
-
-    public void addQueryParam(final String key, final String[] values) {
-        additionalQueryParamMap.put(key, values);
-    }
-
-    public static class QueryPart {
-        protected String value;
-
-        protected boolean parsed;
-
-        public QueryPart(final String value) {
-            this(value, false);
-        }
-
-        public QueryPart(final String value, final boolean parsed) {
-            this.value = value;
-            this.parsed = parsed;
-        }
-
-        /**
-         * @return the parsed
-         */
-        public boolean isParsed() {
-            return parsed;
-        }
-
-        /**
-         * @return the value
-         */
-        public String getValue() {
-            return value;
-        }
-
-        /*
-         * (non-Javadoc)
-         * 
-         * @see java.lang.Object#toString()
-         */
-        @Override
-        public String toString() {
-            return "QueryPart [value=" + value + ", parsed=" + parsed + "]";
-        }
-    }
-
-}

+ 14 - 13
src/main/java/jp/sf/fess/service/SearchService.java

@@ -49,10 +49,10 @@ import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.request.FieldAnalysisRequest;
 import org.apache.solr.client.solrj.request.FieldAnalysisRequest;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.NamedList;
+import org.codelibs.core.util.StringUtil;
 import org.codelibs.solr.lib.SolrGroup;
 import org.codelibs.solr.lib.SolrGroup;
 import org.codelibs.solr.lib.SolrGroupManager;
 import org.codelibs.solr.lib.SolrGroupManager;
 import org.codelibs.solr.lib.policy.QueryType;
 import org.codelibs.solr.lib.policy.QueryType;
-import org.codelibs.core.util.StringUtil;
 
 
 public class SearchService implements Serializable {
 public class SearchService implements Serializable {
 
 
@@ -74,13 +74,13 @@ public class SearchService implements Serializable {
     protected Suggester suggester;
     protected Suggester suggester;
 
 
     public Map<String, Object> getDocument(final String query) {
     public Map<String, Object> getDocument(final String query) {
-        return getDocument(query, null);
+        return getDocument(query, queryHelper.getResponseFields(), null);
     }
     }
 
 
     public Map<String, Object> getDocument(final String query,
     public Map<String, Object> getDocument(final String query,
-            final String[] docValuesFields) {
+            final String[] responseFields, final String[] docValuesFields) {
         final List<Map<String, Object>> docList = getDocumentList(query, 0, 1,
         final List<Map<String, Object>> docList = getDocumentList(query, 0, 1,
-                null, null, null, docValuesFields);
+                null, null, null, responseFields, docValuesFields);
         if (!docList.isEmpty()) {
         if (!docList.isEmpty()) {
             return docList.get(0);
             return docList.get(0);
         }
         }
@@ -88,8 +88,8 @@ public class SearchService implements Serializable {
     }
     }
 
 
     public List<Map<String, Object>> getDocumentListByDocIds(
     public List<Map<String, Object>> getDocumentListByDocIds(
-            final String[] docIds, final String[] docValuesFields,
-            final int pageSize) {
+            final String[] docIds, final String[] responseFields,
+            final String[] docValuesFields, final int pageSize) {
         if (docIds == null || docIds.length == 0) {
         if (docIds == null || docIds.length == 0) {
             return Collections.emptyList();
             return Collections.emptyList();
         }
         }
@@ -102,21 +102,22 @@ public class SearchService implements Serializable {
             buf.append("docId:").append(docIds[i]);
             buf.append("docId:").append(docIds[i]);
         }
         }
         return getDocumentList(buf.toString(), 0, pageSize, null, null, null,
         return getDocumentList(buf.toString(), 0, pageSize, null, null, null,
-                docValuesFields);
+                responseFields, docValuesFields);
     }
     }
 
 
     public List<Map<String, Object>> getDocumentList(final String query,
     public List<Map<String, Object>> getDocumentList(final String query,
             final int start, final int rows, final FacetInfo facetInfo,
             final int start, final int rows, final FacetInfo facetInfo,
             final GeoInfo geoInfo, final MoreLikeThisInfo mltInfo,
             final GeoInfo geoInfo, final MoreLikeThisInfo mltInfo,
-            final String[] docValuesFields) {
+            final String[] responseFields, final String[] docValuesFields) {
         return getDocumentList(query, start, rows, facetInfo, geoInfo, mltInfo,
         return getDocumentList(query, start, rows, facetInfo, geoInfo, mltInfo,
-                docValuesFields, true);
+                responseFields, docValuesFields, true);
     }
     }
 
 
     public List<Map<String, Object>> getDocumentList(final String query,
     public List<Map<String, Object>> getDocumentList(final String query,
             final int start, final int rows, final FacetInfo facetInfo,
             final int start, final int rows, final FacetInfo facetInfo,
             final GeoInfo geoInfo, final MoreLikeThisInfo mltInfo,
             final GeoInfo geoInfo, final MoreLikeThisInfo mltInfo,
-            final String[] docValuesFields, final boolean forUser) {
+            final String[] responseFields, final String[] docValuesFields,
+            final boolean forUser) {
         if (start > queryHelper.getMaxSearchResultOffset()) {
         if (start > queryHelper.getMaxSearchResultOffset()) {
             throw new ResultOffsetExceededException(
             throw new ResultOffsetExceededException(
                     "The number of result size is exceeded.");
                     "The number of result size is exceeded.");
@@ -133,7 +134,7 @@ public class SearchService implements Serializable {
         final String q = searchQuery.getQuery();
         final String q = searchQuery.getQuery();
         if (StringUtil.isNotBlank(q)) {
         if (StringUtil.isNotBlank(q)) {
             // fields
             // fields
-            solrQuery.setFields(queryHelper.getResponseFields());
+            solrQuery.setFields(responseFields);
             // query
             // query
             solrQuery.setQuery(q);
             solrQuery.setQuery(q);
             solrQuery.setStart(start);
             solrQuery.setStart(start);
@@ -152,7 +153,7 @@ public class SearchService implements Serializable {
             if (sortFields.length != 0) {
             if (sortFields.length != 0) {
                 for (final SortField sortField : sortFields) {
                 for (final SortField sortField : sortFields) {
                     solrQuery
                     solrQuery
-                            .setSortField(
+                            .addSort(
                                     sortField.getField(),
                                     sortField.getField(),
                                     Constants.DESC.equals(sortField.getOrder()) ? SolrQuery.ORDER.desc
                                     Constants.DESC.equals(sortField.getOrder()) ? SolrQuery.ORDER.desc
                                             : SolrQuery.ORDER.asc);
                                             : SolrQuery.ORDER.asc);
@@ -161,7 +162,7 @@ public class SearchService implements Serializable {
                 for (final SortField sortField : queryHelper
                 for (final SortField sortField : queryHelper
                         .getDefaultSortFields()) {
                         .getDefaultSortFields()) {
                     solrQuery
                     solrQuery
-                            .setSortField(
+                            .addSort(
                                     sortField.getField(),
                                     sortField.getField(),
                                     Constants.DESC.equals(sortField.getOrder()) ? SolrQuery.ORDER.desc
                                     Constants.DESC.equals(sortField.getOrder()) ? SolrQuery.ORDER.desc
                                             : SolrQuery.ORDER.asc);
                                             : SolrQuery.ORDER.asc);

+ 5 - 1
src/main/java/jp/sf/fess/util/ResourceUtil.java

@@ -23,8 +23,8 @@ import java.util.regex.Pattern;
 
 
 import javax.servlet.ServletContext;
 import javax.servlet.ServletContext;
 
 
-import org.seasar.framework.container.SingletonS2Container;
 import org.codelibs.core.util.StringUtil;
 import org.codelibs.core.util.StringUtil;
+import org.seasar.framework.container.SingletonS2Container;
 import org.seasar.struts.util.ServletContextUtil;
 import org.seasar.struts.util.ServletContextUtil;
 
 
 public class ResourceUtil {
 public class ResourceUtil {
@@ -52,6 +52,10 @@ public class ResourceUtil {
         return getBasePath("WEB-INF/mail/", name);
         return getBasePath("WEB-INF/mail/", name);
     }
     }
 
 
+    public static String getViewTemplatePath(final String name) {
+        return getBasePath("WEB-INF/view/", name);
+    }
+
     protected static String getBasePath(final String baseName, final String name) {
     protected static String getBasePath(final String baseName, final String name) {
 
 
         String path = null;
         String path = null;

+ 1 - 1
src/main/resources/app.dicon

@@ -27,7 +27,7 @@
 	</component>
 	</component>
 	<component name="labelTypeHelper" class="jp.sf.fess.helper.LabelTypeHelper">
 	<component name="labelTypeHelper" class="jp.sf.fess.helper.LabelTypeHelper">
 	</component>
 	</component>
-	<component name="queryHelper" class="jp.sf.fess.helper.impl.QueryHelperImpl">
+	<component name="queryHelper" class="jp.sf.fess.helper.QueryHelper">
 		<property name="defaultFacetInfo">
 		<property name="defaultFacetInfo">
 			<component class="jp.sf.fess.entity.FacetInfo">
 			<component class="jp.sf.fess.entity.FacetInfo">
 				<property name="minCount">1</property>
 				<property name="minCount">1</property>

+ 1 - 0
src/main/resources/application.properties

@@ -72,6 +72,7 @@ errors.not_load_from_server=Could not load from this server: {0}
 errors.failed_to_start_job=Failed to start job {0}.
 errors.failed_to_start_job=Failed to start job {0}.
 errors.failed_to_stop_job=Failed to stop job {0}.
 errors.failed_to_stop_job=Failed to stop job {0}.
 errors.expired_dict_id=Expired dictionary information. Please reload it.
 errors.expired_dict_id=Expired dictionary information. Please reload it.
+errors.failed_to_create_cache=Failed to create a cache reponse for ID:{0}.
 
 
 errors.invalid_query_unknown=The given query is invalid.
 errors.invalid_query_unknown=The given query is invalid.
 errors.invalid_query_quoted=An invalid quote character is used.
 errors.invalid_query_quoted=An invalid quote character is used.

+ 1 - 0
src/main/resources/application_ja.properties

@@ -72,6 +72,7 @@ errors.not_load_from_server=\u3053\u306e\u30b5\u30fc\u30d0\u304b\u3089\u30ed\u30
 errors.failed_to_start_job=\u30b8\u30e7\u30d6 {0} \u306e\u958b\u59cb\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
 errors.failed_to_start_job=\u30b8\u30e7\u30d6 {0} \u306e\u958b\u59cb\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
 errors.failed_to_stop_job=\u30b8\u30e7\u30d6 {0} \u306e\u958b\u59cb\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
 errors.failed_to_stop_job=\u30b8\u30e7\u30d6 {0} \u306e\u958b\u59cb\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
 errors.expired_dict_id=\u8f9e\u66f8\u60c5\u5831\u304c\u671f\u9650\u5207\u308c\u3067\u3059\u3002\u518d\u5ea6\u8aad\u307f\u306a\u304a\u3057\u3066\u304f\u3060\u3055\u3044\u3002
 errors.expired_dict_id=\u8f9e\u66f8\u60c5\u5831\u304c\u671f\u9650\u5207\u308c\u3067\u3059\u3002\u518d\u5ea6\u8aad\u307f\u306a\u304a\u3057\u3066\u304f\u3060\u3055\u3044\u3002
+errors.failed_to_create_cache=ID:{0}\u306e\u30ad\u30e3\u30c3\u30b7\u30e5\u304c\u751f\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002
 
 
 errors.invalid_query_unknown=\u691c\u7d22\u30af\u30a8\u30ea\u304c\u6b63\u3057\u304f\u3042\u308a\u307e\u305b\u3093\u3002
 errors.invalid_query_unknown=\u691c\u7d22\u30af\u30a8\u30ea\u304c\u6b63\u3057\u304f\u3042\u308a\u307e\u305b\u3093\u3002
 errors.invalid_query_quoted=\u30af\u30aa\u30fc\u30c8\u6587\u5b57(")\u306e\u5229\u7528\u65b9\u6cd5\u304c\u6b63\u3057\u304f\u3042\u308a\u307e\u305b\u3093\u3002
 errors.invalid_query_quoted=\u30af\u30aa\u30fc\u30c8\u6587\u5b57(")\u306e\u5229\u7528\u65b9\u6cd5\u304c\u6b63\u3057\u304f\u3042\u308a\u307e\u305b\u3093\u3002

+ 7 - 0
src/main/webapp/WEB-INF/view/cache.hbs

@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<base href="{{url}}">
+<div style="border:1px solid #999;margin:5px -1px;padding:0;">
+<div style="margin:5px 15px;padding:5px;text-align:left;">This content is cached in Fess.</div>
+</div>
+{{{cache}}}

+ 3 - 3
src/main/webapp/WEB-INF/view/searchResults.jsp

@@ -35,10 +35,10 @@
 						<div class="description">${doc.contentDescription}</div>
 						<div class="description">${doc.contentDescription}</div>
 						<div class="site ellipsis">
 						<div class="site ellipsis">
 							<cite>${f:h(doc.site)}</cite>
 							<cite>${f:h(doc.site)}</cite>
-<!--							<c:if test="${doc.hasCache_s_s=='true'}">
-								<a href="#${doc.docId}" class="cache"><bean:message
+							<c:if test="${doc.hasCache_s_s=='true'}">
+								<a href="cache?docId=${doc.docId}" class="cache"><bean:message
 										key="labels.search_result_cache" /></a>
 										key="labels.search_result_cache" /></a>
-							</c:if> -->
+							</c:if>
 						</div>
 						</div>
 						<div class="more visible-phone">
 						<div class="more visible-phone">
 							<a href="#result${s.index}"><bean:message key="labels.search_result_more" /></a>
 							<a href="#result${s.index}"><bean:message key="labels.search_result_more" /></a>

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 253 - 270
src/test/java/jp/sf/fess/helper/QueryHelperTest.java


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác