Browse Source

GSA API support

Kaoru FUZITA 10 years ago
parent
commit
66e5be9241

+ 2 - 0
src/main/java/jp/sf/fess/Constants.java

@@ -36,6 +36,8 @@ public class Constants extends CoreLibConstants {
 
 
     public static final String WEB_API_VERSION = FESS_VERSION;
     public static final String WEB_API_VERSION = FESS_VERSION;
 
 
+    public static final String GSA_API_VERSION = "3.2";
+
     public static final String TRUE = "true";
     public static final String TRUE = "true";
 
 
     public static final String FALSE = "false";
     public static final String FALSE = "false";

+ 65 - 2
src/main/java/jp/sf/fess/action/IndexAction.java

@@ -129,6 +129,15 @@ public class IndexAction {
     protected static final Pattern FIELD_EXTRACTION_PATTERN = Pattern
     protected static final Pattern FIELD_EXTRACTION_PATTERN = Pattern
             .compile("^([a-zA-Z0-9_]+):.*");
             .compile("^([a-zA-Z0-9_]+):.*");
 
 
+    // GSA
+    private static final String GSA_QUERY = "q";
+
+    private static final String GSA_SITE = "site";
+
+    private static final String GSA_REQUIRED_FIELDS = "requiredfields";
+
+    private static final String GSA_GET_FIELDS = "getfields";
+
     @ActionForm
     @ActionForm
     @Resource
     @Resource
     protected IndexForm indexForm;
     protected IndexForm indexForm;
@@ -593,6 +602,60 @@ public class IndexAction {
         return null;
         return null;
     }
     }
 
 
+    @Execute(validator = false)
+    public String gsaSearchApi() {
+        try {
+            // query words
+            indexForm.query = (String) request.getParameter(GSA_QUERY);
+            WebApiUtil.setObject("searchQueryOriginal", indexForm.query);
+            // collections
+            List<String> additional = new ArrayList<String>();
+            final String site = (String) request.getParameter(GSA_SITE);
+            if (StringUtil.isNotBlank(site)) {
+                additional.add(" (label:"
+                        + site.replace(".", " AND label:").replace("|",
+                                " OR label:") + ")");
+            }
+            // dynmic fields
+            final String requiredFields = (String) request
+                    .getParameter(GSA_REQUIRED_FIELDS);
+            if (StringUtil.isNotBlank(requiredFields)) {
+                additional.add(" (MT_"
+                        + requiredFields.replace(":", "_s:")
+                                .replace(".", " AND MT_")
+                                .replace("|", " OR MT_") + ")");
+            }
+            if (additional.size() > 0) {
+                indexForm.additional = (String[]) additional
+                        .toArray(new String[additional.size()]);
+            }
+            // meta tags should be returned
+            final String getFields = (String) request
+                    .getParameter(GSA_GET_FIELDS);
+            if (StringUtil.isNotBlank(getFields)) {
+                WebApiUtil.setObject("getFields",
+                        Arrays.asList(getFields.split("\\.")));
+            } else {
+                WebApiUtil.setObject("getFields", new ArrayList<String>());
+            }
+
+            WebApiUtil.setObject("searchQuery", doSearchInternal());
+            WebApiUtil.setObject("searchTime", searchTime);
+            WebApiUtil.setObject("queryTime", queryTime);
+            WebApiUtil.setObject("execTime", execTime);
+            WebApiUtil.setObject("pageSize", pageSize);
+            WebApiUtil.setObject("currentPageNumber", currentPageNumber);
+            WebApiUtil.setObject("allRecordCount", allRecordCount);
+            WebApiUtil.setObject("allPageCount", allPageCount);
+            WebApiUtil.setObject("documentItems", documentItems);
+            WebApiUtil.setObject("facetResponse", facetResponse);
+            WebApiUtil.setObject("moreLikeThisResponse", moreLikeThisResponse);
+        } catch (final Exception e) {
+            WebApiUtil.setError(1, e);
+        }
+        return null;
+    }
+
     @Execute(validator = false)
     @Execute(validator = false)
     public String suggestApi() {
     public String suggestApi() {
         if (Constants.FALSE.equals(crawlerProperties.getProperty(
         if (Constants.FALSE.equals(crawlerProperties.getProperty(
@@ -621,7 +684,7 @@ public class IndexAction {
         }
         }
 
 
         final String[] fieldNames = indexForm.fn;
         final String[] fieldNames = indexForm.fn;
-        final String[] labels = indexForm.fields.get("label");
+        final String[] labels = indexForm.fields.get(LABEL_FIELD);
 
 
         final List<SuggestResponse> suggestResultList = new ArrayList<SuggestResponse>();
         final List<SuggestResponse> suggestResultList = new ArrayList<SuggestResponse>();
         WebApiUtil.setObject("suggestResultList", suggestResultList);
         WebApiUtil.setObject("suggestResultList", suggestResultList);
@@ -687,7 +750,7 @@ public class IndexAction {
         }
         }
 
 
         final String[] fieldNames = indexForm.fn;
         final String[] fieldNames = indexForm.fn;
-        final String[] labels = indexForm.fields.get("label");
+        final String[] labels = indexForm.fields.get(LABEL_FIELD);
 
 
         final List<SpellCheckResponse> spellCheckResultList = new ArrayList<SpellCheckResponse>();
         final List<SpellCheckResponse> spellCheckResultList = new ArrayList<SpellCheckResponse>();
         WebApiUtil.setObject("spellCheckResultList", spellCheckResultList);
         WebApiUtil.setObject("spellCheckResultList", spellCheckResultList);

+ 2 - 0
src/main/java/jp/sf/fess/api/BaseApiManager.java

@@ -31,6 +31,8 @@ public class BaseApiManager {
 
 
     protected static final String SEARCH_API = "/searchApi";
     protected static final String SEARCH_API = "/searchApi";
 
 
+    protected static final String GSA_SEARCH_API = "/gsaSearchApi";
+
     protected static enum FormatType {
     protected static enum FormatType {
         SEARCH, LABEL, SUGGEST, SPELLCHECK, ANALYSIS, HOTSEARCHWORD, FAVORITE, FAVORITES, OTHER, PING;
         SEARCH, LABEL, SUGGEST, SPELLCHECK, ANALYSIS, HOTSEARCHWORD, FAVORITE, FAVORITES, OTHER, PING;
     }
     }

+ 670 - 0
src/main/java/jp/sf/fess/api/xml/GsaApiManager.java

@@ -0,0 +1,670 @@
+/*
+ * Copyright 2009-2015 the CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package jp.sf.fess.api.xml;
+
+import java.io.IOException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import jp.sf.fess.Constants;
+import jp.sf.fess.WebApiException;
+import jp.sf.fess.api.BaseApiManager;
+import jp.sf.fess.api.WebApiManager;
+import jp.sf.fess.api.WebApiRequest;
+import jp.sf.fess.api.WebApiResponse;
+import jp.sf.fess.db.allcommon.CDef;
+import jp.sf.fess.entity.FieldAnalysisResponse;
+import jp.sf.fess.entity.PingResponse;
+import jp.sf.fess.entity.PingResponse.Target;
+import jp.sf.fess.service.SearchService;
+import jp.sf.fess.suggest.entity.SpellCheckResponse;
+import jp.sf.fess.suggest.entity.SuggestResponse;
+import jp.sf.fess.suggest.entity.SuggestResponse.SuggestResponseList;
+import jp.sf.fess.util.ComponentUtil;
+import jp.sf.fess.util.FacetResponse;
+import jp.sf.fess.util.FacetResponse.Field;
+import jp.sf.fess.util.MoreLikeThisResponse;
+import jp.sf.fess.util.WebApiUtil;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.codelibs.core.CoreLibConstants;
+import org.codelibs.core.util.StringUtil;
+import org.seasar.struts.util.ResponseUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class GsaApiManager extends BaseApiManager implements WebApiManager {
+    private static final Logger logger = LoggerFactory
+            .getLogger(GsaApiManager.class);
+
+    protected String gsaPathPrefix = "/gsa";
+
+    private static final String GSA_META_PREFIX = "MT_";
+
+    private static final String GSA_META_SUFFIX = "_s";
+
+    @Override
+    public boolean matches(final HttpServletRequest request) {
+        if (Constants.FALSE.equals(ComponentUtil.getCrawlerProperties()
+                .getProperty(Constants.WEB_API_XML_PROPERTY, Constants.TRUE))) {
+            return false;
+        }
+
+        final String servletPath = request.getServletPath();
+        return servletPath.startsWith(gsaPathPrefix);
+    }
+
+    @Override
+    public void process(final HttpServletRequest request,
+            final HttpServletResponse response, final FilterChain chain)
+            throws IOException, ServletException {
+        final String formatType = request.getParameter("type");
+        switch (getFormatType(formatType)) {
+            case SEARCH:
+                processSearchRequest(request, response, chain);
+                break;
+            case LABEL:
+                processLabelRequest(request, response, chain);
+                break;
+            case SUGGEST:
+                processSuggestRequest(request, response, chain);
+                break;
+            case SPELLCHECK:
+                processSpellCheckRequest(request, response, chain);
+                break;
+            case ANALYSIS:
+                processAnalysisRequest(request, response, chain);
+                break;
+            case PING:
+                processPingRequest(request, response, chain);
+                break;
+            default:
+                writeXmlResponse(-1, StringUtil.EMPTY, "Not found.");
+                break;
+        }
+
+    }
+
+    protected void processPingRequest(final HttpServletRequest request,
+            final HttpServletResponse response, final FilterChain chain) {
+        final SearchService searchService = ComponentUtil.getSearchService();
+        int status;
+        final StringBuilder buf = new StringBuilder(1000);
+        String errMsg = null;
+        try {
+            final PingResponse pingResponse = searchService.ping();
+            status = pingResponse.getStatus();
+            buf.append("<result>");
+            for (final Target target : pingResponse.getTargets()) {
+                buf.append("<server><status>");
+                buf.append(target.getStatus());
+                buf.append("</status><url>");
+                buf.append(escapeXml(target.getUrl()));
+                buf.append("</url><query-time>");
+                buf.append(target.getQueryTime());
+                buf.append("</query-time><search-time>");
+                buf.append(target.getSearchTime());
+                buf.append("</search-time></server>");
+            }
+            buf.append("</result>");
+        } catch (final Exception e) {
+            status = 9;
+            errMsg = e.getMessage();
+            if (errMsg == null) {
+                errMsg = e.getClass().getName();
+            }
+            if (logger.isDebugEnabled()) {
+                logger.debug("Failed to process a ping request.", e);
+            }
+        }
+
+        writeXmlResponse(status, buf.toString(), errMsg);
+    }
+
+    protected void processSearchRequest(final HttpServletRequest request,
+            final HttpServletResponse response, final FilterChain chain) {
+        int status = 0;
+        String errMsg = StringUtil.EMPTY;
+        final StringBuilder buf = new StringBuilder(1000);
+        String query = null;
+        request.setAttribute(Constants.SEARCH_LOG_ACCESS_TYPE,
+                CDef.AccessType.Xml);
+        try {
+            chain.doFilter(new WebApiRequest(request, GSA_SEARCH_API),
+                    new WebApiResponse(response));
+            WebApiUtil.validate();
+            query = WebApiUtil.getObject("searchQueryOriginal");
+            final String execTime = WebApiUtil.getObject("execTime");
+            final int pageSize = Integer.parseInt((String) WebApiUtil.getObject("pageSize"));
+            final int allRecordCount = Integer.parseInt((String) WebApiUtil
+                    .getObject("allRecordCount"));
+            final List<Map<String, Object>> documentItems = WebApiUtil
+                    .getObject("documentItems");
+            final String start = request.getParameter("start");
+            int startNumber = 1;
+            if (StringUtil.isNotBlank(start)) {
+                startNumber = Integer.parseInt(start) + 1;
+            }
+            int endNumber = startNumber + pageSize -1;
+            if (endNumber > allRecordCount) {
+            	endNumber = allRecordCount;
+            }
+            final FacetResponse facetResponse = WebApiUtil
+                    .getObject("facetResponse");
+            final MoreLikeThisResponse moreLikeThisResponse = WebApiUtil
+                    .getObject("moreLikeThisResponse");
+            List<String> getFields = WebApiUtil.getObject("getFields");
+
+            buf.append("<Q>");
+            buf.append(escapeXml(query));
+            buf.append("</Q>");
+            buf.append("<TM>");
+            buf.append(execTime);
+            buf.append("</TM>");
+            for (final Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
+                final String[] values = entry.getValue();
+                if (values == null) {
+                    continue;
+                }
+                final String key = entry.getKey();
+                for (final String value : values) {
+                    buf.append("<PARAM name=\"");
+                    buf.append(key);
+                    buf.append("\" value=\"");
+                    buf.append(value);
+                    buf.append("\" original_value=\"");
+                    // TODO: should be saved original input value
+                    buf.append(URLEncoder.encode(value, Constants.UTF_8));
+                    buf.append("\"/>");
+                }
+            }
+            buf.append("<M>");
+            buf.append(allRecordCount);
+            buf.append("</M>");
+            if (documentItems.size() > 0) {
+                buf.append("<RES SN=\"");
+                buf.append(startNumber);
+                buf.append("\" EN=\"");
+                buf.append(endNumber);
+                buf.append("\">");
+                int recordNumber = startNumber;
+                for (Map<String, Object> document : documentItems) {
+                    buf.append("<R N=\"");
+                    buf.append(recordNumber);
+                    buf.append("\">");
+                    final String url = (String) document.remove("url");
+                    document.put("UE", url);
+                    document.put("U", URLDecoder.decode(url, Constants.UTF_8));
+                    document.put("T", document.remove("title"));
+                    document.put("RK", document.remove("sore"));
+                    document.put("S", ((String) document
+                            .remove("contentDescription")).replaceAll(
+                            "<(/*)em>", "<$1b>"));
+                    for (final Map.Entry<String, Object> entry : document
+                            .entrySet()) {
+                        final String name = entry.getKey();
+                        if (StringUtil.isNotBlank(name)
+                                && entry.getValue() != null
+                                && ComponentUtil.getQueryHelper()
+                                        .isApiResponseField(name)) {
+                            if (name.startsWith(GSA_META_PREFIX)) {
+                                final String tagName = name.replaceAll(
+                                        "^" + GSA_META_PREFIX, "").replaceAll(
+                                        GSA_META_SUFFIX + "\\z", "");
+                                if (getFields.contains(tagName)) {
+                                    buf.append("<MT N=\"");
+                                    buf.append(tagName);
+                                    buf.append("\" V=\"");
+                                    buf.append(escapeXml(entry.getValue()
+                                            .toString()));
+                                    buf.append("\"/>");
+                                }
+                            } else {
+                                final String tagName = name;
+                                buf.append('<');
+                                buf.append(tagName);
+                                buf.append('>');
+                                buf.append(escapeXml(entry.getValue()));
+                                buf.append("</");
+                                buf.append(tagName);
+                                buf.append('>');
+                            }
+                        }
+                    }
+                    buf.append("</R>");
+                    recordNumber++;
+                }
+                buf.append("</RES>");
+            } else {
+                buf.append("<RES/>");
+            }
+            if (facetResponse != null && facetResponse.hasFacetResponse()) {
+                buf.append("<facet>");
+                // facet field
+                if (facetResponse.getFieldList() != null) {
+                    for (final Field field : facetResponse.getFieldList()) {
+                        buf.append("<field name=\"");
+                        buf.append(escapeXml(field.getName()));
+                        buf.append("\">");
+                        for (final Map.Entry<String, Long> entry : field
+                                .getValueCountMap().entrySet()) {
+                            buf.append("<value count=\"");
+                            buf.append(escapeXml(entry.getValue()));
+                            buf.append("\">");
+                            buf.append(escapeXml(entry.getKey()));
+                            buf.append("</value>");
+                        }
+                        buf.append("</field>");
+                    }
+                }
+                // facet query
+                if (facetResponse.getQueryCountMap() != null) {
+                    buf.append("<query>");
+                    for (final Map.Entry<String, Long> entry : facetResponse
+                            .getQueryCountMap().entrySet()) {
+                        buf.append("<value count=\"");
+                        buf.append(escapeXml(entry.getValue()));
+                        buf.append("\">");
+                        buf.append(escapeXml(entry.getKey()));
+                        buf.append("</value>");
+                    }
+                    buf.append("</query>");
+                }
+                buf.append("</facet>");
+            }
+            if (moreLikeThisResponse != null && !moreLikeThisResponse.isEmpty()) {
+                buf.append("<more-like-this>");
+                for (final Map.Entry<String, List<Map<String, Object>>> mltEntry : moreLikeThisResponse
+                        .entrySet()) {
+                    buf.append("<result id=\"");
+                    buf.append(escapeXml(mltEntry.getKey()));
+                    buf.append("\">");
+                    for (final Map<String, Object> document : mltEntry
+                            .getValue()) {
+                        buf.append("<doc>");
+                        for (final Map.Entry<String, Object> entry : document
+                                .entrySet()) {
+                            if (StringUtil.isNotBlank(entry.getKey())
+                                    && entry.getValue() != null) {
+                                final String tagName = convertTagName(entry
+                                        .getKey());
+                                buf.append('<');
+                                buf.append(tagName);
+                                buf.append('>');
+                                buf.append(escapeXml(entry.getValue()
+                                        .toString()));
+                                buf.append("</");
+                                buf.append(tagName);
+                                buf.append('>');
+                            }
+                        }
+                        buf.append("</doc>");
+                    }
+                    buf.append("</result>");
+                }
+                buf.append("</more-like-this>");
+            }
+        } catch (final Exception e) {
+            status = 1;
+            errMsg = e.getMessage();
+            if (errMsg == null) {
+                errMsg = e.getClass().getName();
+            }
+            if (logger.isDebugEnabled()) {
+                logger.debug("Failed to process a search request.", e);
+            }
+        }
+
+        writeXmlResponse(status, buf.toString(), errMsg);
+    }
+    
+    private String convertTagName(final String name) {
+        final String tagName = StringUtil.decamelize(name).replaceAll("_", "-")
+                .toLowerCase();
+        return tagName;
+    }
+
+    protected void processLabelRequest(final HttpServletRequest request,
+            final HttpServletResponse response, final FilterChain chain) {
+        int status = 0;
+        String errMsg = StringUtil.EMPTY;
+        final StringBuilder buf = new StringBuilder(255);
+        try {
+            final List<Map<String, String>> labelTypeItems = ComponentUtil
+                    .getLabelTypeHelper().getLabelTypeItemList();
+            buf.append("<record-count>");
+            buf.append(labelTypeItems.size());
+            buf.append("</record-count>");
+            buf.append("<result>");
+            for (final Map<String, String> labelMap : labelTypeItems) {
+                buf.append("<label>");
+                buf.append("<name>");
+                buf.append(escapeXml(labelMap.get(Constants.ITEM_LABEL)));
+                buf.append("</name>");
+                buf.append("<value>");
+                buf.append(escapeXml(labelMap.get(Constants.ITEM_VALUE)));
+                buf.append("</value>");
+                buf.append("</label>");
+            }
+            buf.append("</result>");
+        } catch (final Exception e) {
+            status = 1;
+            errMsg = e.getMessage();
+            if (logger.isDebugEnabled()) {
+                logger.debug("Failed to process a label request.", e);
+            }
+
+        }
+
+        writeXmlResponse(status, buf.toString(), errMsg);
+    }
+
+    protected void processSuggestRequest(final HttpServletRequest request,
+            final HttpServletResponse response, final FilterChain chain) {
+
+        int status = 0;
+        String errMsg = StringUtil.EMPTY;
+        final StringBuilder buf = new StringBuilder(255);
+        try {
+            chain.doFilter(new WebApiRequest(request, SUGGEST_API),
+                    new WebApiResponse(response));
+            WebApiUtil.validate();
+            final Integer suggestRecordCount = WebApiUtil
+                    .getObject("suggestRecordCount");
+            final List<SuggestResponse> suggestResultList = WebApiUtil
+                    .getObject("suggestResultList");
+            final List<String> suggestFieldName = WebApiUtil
+                    .getObject("suggestFieldName");
+
+            buf.append("<record-count>");
+            buf.append(suggestRecordCount);
+            buf.append("</record-count>");
+            if (suggestResultList.size() > 0) {
+                buf.append("<result>");
+
+                for (int i = 0; i < suggestResultList.size(); i++) {
+
+                    final SuggestResponse suggestResponse = suggestResultList
+                            .get(i);
+
+                    for (final Map.Entry<String, List<String>> entry : suggestResponse
+                            .entrySet()) {
+                        final SuggestResponseList srList = (SuggestResponseList) entry
+                                .getValue();
+                        final String fn = suggestFieldName.get(i);
+                        buf.append("<suggest>");
+                        buf.append("<token>");
+                        buf.append(escapeXml(entry.getKey()));
+                        buf.append("</token>");
+                        buf.append("<fn>");
+                        buf.append(escapeXml(fn));
+                        buf.append("</fn>");
+                        buf.append("<start-offset>");
+                        buf.append(escapeXml(Integer.toString(srList
+                                .getStartOffset())));
+                        buf.append("</start-offset>");
+                        buf.append("<end-offset>");
+                        buf.append(escapeXml(Integer.toString(srList
+                                .getEndOffset())));
+                        buf.append("</end-offset>");
+                        buf.append("<num-found>");
+                        buf.append(escapeXml(Integer.toString(srList
+                                .getNumFound())));
+                        buf.append("</num-found>");
+                        buf.append("<result>");
+                        for (final String value : srList) {
+                            buf.append("<value>");
+                            buf.append(escapeXml(value));
+                            buf.append("</value>");
+                        }
+                        buf.append("</result>");
+                        buf.append("</suggest>");
+
+                    }
+                }
+                buf.append("</result>");
+            }
+        } catch (final Exception e) {
+            if (e instanceof WebApiException) {
+                status = ((WebApiException) e).getStatusCode();
+            } else {
+                status = 1;
+            }
+            errMsg = e.getMessage();
+            if (logger.isDebugEnabled()) {
+                logger.debug("Failed to process a suggest request.", e);
+            }
+        }
+
+        writeXmlResponse(status, buf.toString(), errMsg);
+    }
+
+    protected void processSpellCheckRequest(final HttpServletRequest request,
+            final HttpServletResponse response, final FilterChain chain) {
+
+        int status = 0;
+        String errMsg = StringUtil.EMPTY;
+        final StringBuilder buf = new StringBuilder(255);
+        try {
+            chain.doFilter(new WebApiRequest(request, SPELLCHECK_API),
+                    new WebApiResponse(response));
+            WebApiUtil.validate();
+            final Integer spellCheckRecordCount = WebApiUtil
+                    .getObject("spellCheckRecordCount");
+            final List<SpellCheckResponse> spellCheckResultList = WebApiUtil
+                    .getObject("spellCheckResultList");
+            final List<String> spellCheckFieldName = WebApiUtil
+                    .getObject("spellCheckFieldName");
+
+            buf.append("<record-count>");
+            buf.append(spellCheckRecordCount);
+            buf.append("</record-count>");
+            if (spellCheckResultList.size() > 0) {
+                buf.append("<result>");
+
+                for (int i = 0; i < spellCheckResultList.size(); i++) {
+
+                    final SuggestResponse suggestResponse = spellCheckResultList
+                            .get(i);
+
+                    for (final Map.Entry<String, List<String>> entry : suggestResponse
+                            .entrySet()) {
+                        final SuggestResponseList srList = (SuggestResponseList) entry
+                                .getValue();
+                        final String fn = spellCheckFieldName.get(i);
+                        buf.append("<suggest>");
+                        buf.append("<token>");
+                        buf.append(escapeXml(entry.getKey()));
+                        buf.append("</token>");
+                        buf.append("<fn>");
+                        buf.append(escapeXml(fn));
+                        buf.append("</fn>");
+                        buf.append("<start-offset>");
+                        buf.append(escapeXml(Integer.toString(srList
+                                .getStartOffset())));
+                        buf.append("</start-offset>");
+                        buf.append("<end-offset>");
+                        buf.append(escapeXml(Integer.toString(srList
+                                .getEndOffset())));
+                        buf.append("</end-offset>");
+                        buf.append("<num-found>");
+                        buf.append(escapeXml(Integer.toString(srList
+                                .getNumFound())));
+                        buf.append("</num-found>");
+                        buf.append("<result>");
+                        for (final String value : srList) {
+                            buf.append("<value>");
+                            buf.append(escapeXml(value));
+                            buf.append("</value>");
+                        }
+                        buf.append("</result>");
+                        buf.append("</suggest>");
+
+                    }
+                }
+                buf.append("</result>");
+            }
+        } catch (final Exception e) {
+            if (e instanceof WebApiException) {
+                status = ((WebApiException) e).getStatusCode();
+            } else {
+                status = 1;
+            }
+            errMsg = e.getMessage();
+            if (logger.isDebugEnabled()) {
+                logger.debug("Failed to process a spellcheck request.", e);
+            }
+        }
+
+        writeXmlResponse(status, buf.toString(), errMsg);
+    }
+
+    protected String processAnalysisRequest(final HttpServletRequest request,
+            final HttpServletResponse response, final FilterChain chain) {
+
+        int status = 0;
+        String errMsg = StringUtil.EMPTY;
+        final StringBuilder buf = new StringBuilder(255);
+        try {
+            chain.doFilter(new WebApiRequest(request, ANALYSIS_API),
+                    new WebApiResponse(response));
+            WebApiUtil.validate();
+            final FieldAnalysisResponse fieldAnalysis = WebApiUtil
+                    .getObject("fieldAnalysis");
+
+            buf.append("<record-count>");
+            buf.append(fieldAnalysis.size());
+            buf.append("</record-count>");
+            if (fieldAnalysis.size() > 0) {
+                buf.append("<result>");
+                for (final Map.Entry<String, Map<String, List<Map<String, Object>>>> fEntry : fieldAnalysis
+                        .entrySet()) {
+
+                    buf.append("<field name=\"")
+                            .append(escapeXml(fEntry.getKey())).append("\">");
+                    for (final Map.Entry<String, List<Map<String, Object>>> aEntry : fEntry
+                            .getValue().entrySet()) {
+                        buf.append("<analysis name=\"")
+                                .append(escapeXml(aEntry.getKey()))
+                                .append("\">");
+                        for (final Map<String, Object> dataMap : aEntry
+                                .getValue()) {
+                            buf.append("<token>");
+                            for (final Map.Entry<String, Object> dEntry : dataMap
+                                    .entrySet()) {
+                                final String key = dEntry.getKey();
+                                final Object value = dEntry.getValue();
+                                if (StringUtil.isNotBlank(key) && value != null) {
+                                    buf.append("<value name=\"")
+                                            .append(escapeXml(key))
+                                            .append("\">")
+                                            .append(escapeXml(value))
+                                            .append("</value>");
+                                }
+                            }
+                            buf.append("</token>");
+                        }
+                        buf.append("</analysis>");
+                    }
+                    buf.append("</field>");
+                }
+                buf.append("</result>");
+            }
+
+        } catch (final Exception e) {
+            if (e instanceof WebApiException) {
+                status = ((WebApiException) e).getStatusCode();
+            } else {
+                status = 1;
+            }
+            errMsg = e.getMessage();
+            if (logger.isDebugEnabled()) {
+                logger.debug("Failed to process a suggest request.", e);
+            }
+        }
+
+        writeXmlResponse(status, buf.toString(), errMsg);
+        return null;
+    }
+
+    protected void writeXmlResponse(final int status, final String body,
+            final String errMsg) {
+        final StringBuilder buf = new StringBuilder(1000);
+        buf.append("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>");
+        buf.append("<GSP VER=\"");
+        buf.append(Constants.GSA_API_VERSION);
+        buf.append("\">");
+//        buf.append("<status>");
+//        buf.append(status);
+//        buf.append("</status>");
+        if (status == 0) {
+            buf.append(body);
+        } else {
+            buf.append("<message>");
+            buf.append(escapeXml(errMsg));
+            buf.append("</message>");
+        }
+        buf.append("</GSP>");
+        ResponseUtil.write(buf.toString(), "text/xml", Constants.UTF_8);
+
+    }
+
+    protected String escapeXml(final Object obj) {
+        final StringBuilder buf = new StringBuilder(255);
+        if (obj instanceof List<?>) {
+            buf.append("<list>");
+            for (final Object child : (List<?>) obj) {
+                buf.append("<item>").append(escapeXml(child)).append("</item>");
+            }
+            buf.append("</list>");
+        } else if (obj instanceof Map<?, ?>) {
+            buf.append("<data>");
+            for (final Map.Entry<?, ?> entry : ((Map<?, ?>) obj).entrySet()) {
+
+                buf.append("<name>").append(escapeXml(entry.getKey()))
+                        .append("</name><value>")
+                        .append(escapeXml(entry.getValue())).append("</value>");
+            }
+            buf.append("</data>");
+        } else if (obj instanceof Date) {
+            final SimpleDateFormat sdf = new SimpleDateFormat(
+                    CoreLibConstants.DATE_FORMAT_ISO_8601_EXTEND);
+            buf.append(StringEscapeUtils.escapeXml(sdf.format(obj)));
+        } else if (obj != null) {
+            buf.append(StringEscapeUtils.escapeXml(obj.toString()));
+        }
+        return buf.toString();
+    }
+
+    public String getXmlPathPrefix() {
+        return gsaPathPrefix;
+    }
+
+    public void setXmlPathPrefix(final String xmlPathPrefix) {
+        this.gsaPathPrefix = xmlPathPrefix;
+    }
+}