com.zimbra.cs.service.mail.CheckSpelling.java Source code

Java tutorial

Introduction

Here is the source code for com.zimbra.cs.service.mail.CheckSpelling.java

Source

/*
 * ***** BEGIN LICENSE BLOCK *****
 * Zimbra Collaboration Suite Server
 * Copyright (C) 2005, 2006, 2007, 2009, 2010, 2011, 2013, 2014, 2016 Synacor, Inc.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software Foundation,
 * version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License along with this program.
 * If not, see <https://www.gnu.org/licenses/>.
 * ***** END LICENSE BLOCK *****
 */

package com.zimbra.cs.service.mail;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.PostMethod;

import com.zimbra.common.httpclient.HttpClientUtil;
import com.zimbra.common.service.ServiceException;
import com.zimbra.common.soap.Element;
import com.zimbra.common.soap.MailConstants;
import com.zimbra.common.util.ArrayUtil;
import com.zimbra.common.util.ListUtil;
import com.zimbra.common.util.Log;
import com.zimbra.common.util.StringUtil;
import com.zimbra.common.util.ZimbraHttpConnectionManager;
import com.zimbra.common.util.ZimbraLog;
import com.zimbra.cs.account.Account;
import com.zimbra.cs.account.Provisioning;
import com.zimbra.cs.account.Server;
import com.zimbra.soap.ZimbraSoapContext;

/**
 * @author bburtin
 */
public class CheckSpelling extends MailDocumentHandler {

    private Log log = ZimbraLog.misc;

    private class ServerResponse {
        int statusCode;
        String content;
    }

    @Override
    public Element handle(Element request, Map<String, Object> context) throws ServiceException {
        ZimbraSoapContext zc = getZimbraSoapContext(context);
        Element response = zc.createElement(MailConstants.CHECK_SPELLING_RESPONSE);

        // Make sure a spell check server is specified
        Provisioning prov = Provisioning.getInstance();
        Server localServer = prov.getLocalServer();
        String[] urls = localServer.getMultiAttr(Provisioning.A_zimbraSpellCheckURL);
        if (ArrayUtil.isEmpty(urls)) {
            log.warn("Unable to check spelling.  No value specified for %s", Provisioning.A_zimbraSpellCheckURL);
            return unavailable(response);
        }

        // Handle no content
        String text = request.getTextTrim();
        if (StringUtil.isNullOrEmpty(text)) {
            log.debug("<CheckSpellingRequest> was empty");
            response.addAttribute(MailConstants.A_AVAILABLE, true);
            return response;
        }

        // Get the dictionary name from either (1) the request,
        // (2) zimbraPrefSpellDictionary or (3) the account's locale.
        String dictionary = request.getAttribute(MailConstants.A_DICTIONARY, null);
        Account account = getRequestedAccount(zc);
        if (dictionary == null) {
            dictionary = account.getPrefSpellDictionary();
            if (dictionary == null) {
                dictionary = account.getLocale().toString();
            }
        }

        // Assemble the list of words to ignore from the account, domain, and COS.
        List<String> ignoreWords = new ArrayList<String>();
        addToList(ignoreWords, account.getPrefSpellIgnoreWord());
        addToList(ignoreWords, prov.getDomain(account).getPrefSpellIgnoreWord());
        addToList(ignoreWords, prov.getCOS(account).getPrefSpellIgnoreWord());

        String ignore = request.getAttribute(MailConstants.A_IGNORE, null);
        if (ignore != null) {
            addToList(ignoreWords, ignore.split("[\\s,]+"));
        }

        String ignorePattern = account.getPrefSpellIgnorePattern();

        // Get word list from one of the spell servers.
        ServerResponse spellResponse = null;
        for (int i = 0; i < urls.length; i++) {
            String url = urls[i];
            try {
                boolean ignoreAllCaps = account.isPrefSpellIgnoreAllCaps();
                log.debug("Checking spelling: url=%s, dictionary=%s, text=%s, ignore=%s, ignoreAllCaps=%b", url,
                        dictionary, text, ignoreWords, ignoreAllCaps);
                spellResponse = checkSpelling(url, dictionary, ignoreWords, text, ignoreAllCaps);
                if (spellResponse.statusCode == 200) {
                    break; // Successful request.  No need to check the other servers.
                }
            } catch (IOException ex) {
                ZimbraLog.mailbox.warn("An error occurred while contacting " + url, ex);
            }
        }

        // Check for errors
        if (spellResponse == null) {
            return unavailable(response);
        }
        if (spellResponse.statusCode != 200) {
            throw ServiceException.FAILURE("Spell check failed: " + spellResponse.content, null);
        }

        // Parse spell server response, assemble SOAP response
        if (spellResponse.content != null) {
            BufferedReader reader = new BufferedReader(new StringReader(spellResponse.content));
            String line = null;
            int numLines = 0;
            int numMisspelled = 0;
            try {
                while ((line = reader.readLine()) != null) {
                    // Each line in the response has the format "werd:word,ward,weird"
                    line = line.trim();
                    numLines++;
                    int colonPos = line.indexOf(':');

                    if (colonPos > 0) {
                        String word = line.substring(0, colonPos);
                        String suggestions = line.substring(colonPos + 1, line.length());
                        if (ignorePattern != null && word.matches(ignorePattern)) {
                            continue;
                        }
                        Element wordEl = response.addElement(MailConstants.E_MISSPELLED);
                        wordEl.addAttribute(MailConstants.A_WORD, word);
                        wordEl.addAttribute(MailConstants.A_SUGGESTIONS, suggestions);
                        numMisspelled++;
                    }
                }
            } catch (IOException e) {
                log.warn("IOException checking spelling", e);
                return unavailable(response);
            }
            log.debug("CheckSpelling: found %d misspelled words in %d lines", numMisspelled, numLines);
        }

        response.addAttribute(MailConstants.A_AVAILABLE, true);
        return response;
    }

    private void addToList(List<String> list, String[] elements) {
        if (elements == null) {
            return;
        }
        for (String element : elements) {
            if (!StringUtil.isNullOrEmpty(element)) {
                list.add(element);
            }
        }
    }

    private ServerResponse checkSpelling(String url, String dictionary, List<String> ignoreWords, String text,
            boolean ignoreAllCaps) throws IOException {
        PostMethod post = new PostMethod(url);
        post.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        if (dictionary != null) {
            post.addParameter("dictionary", dictionary);
        }
        if (text != null) {
            post.addParameter("text", text);
        }
        if (!ListUtil.isEmpty(ignoreWords)) {
            post.addParameter("ignore", StringUtil.join(",", ignoreWords));
        }
        if (ignoreAllCaps) {
            post.addParameter("ignoreAllCaps", "on");
        }
        HttpClient http = ZimbraHttpConnectionManager.getExternalHttpConnMgr().newHttpClient();
        ServerResponse response = new ServerResponse();
        try {
            response.statusCode = HttpClientUtil.executeMethod(http, post);
            response.content = post.getResponseBodyAsString();
        } finally {
            post.releaseConnection();
        }
        return response;
    }

    private Element unavailable(Element response) {
        response.addAttribute(MailConstants.A_AVAILABLE, false);
        return response;
    }
}