001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.kernel.search;
016    
017    import com.liferay.portal.kernel.configuration.Filter;
018    import com.liferay.portal.kernel.log.Log;
019    import com.liferay.portal.kernel.log.LogFactoryUtil;
020    import com.liferay.portal.kernel.nio.charset.CharsetEncoderUtil;
021    import com.liferay.portal.kernel.util.Base64;
022    import com.liferay.portal.kernel.util.Digester;
023    import com.liferay.portal.kernel.util.DigesterUtil;
024    import com.liferay.portal.kernel.util.PortalClassLoaderUtil;
025    import com.liferay.portal.kernel.util.PropsKeys;
026    import com.liferay.portal.kernel.util.PropsUtil;
027    import com.liferay.portal.kernel.util.StreamUtil;
028    import com.liferay.portal.kernel.util.StringBundler;
029    import com.liferay.portal.kernel.util.StringPool;
030    import com.liferay.portal.kernel.util.StringUtil;
031    import com.liferay.portal.kernel.util.Validator;
032    import com.liferay.portal.model.Group;
033    import com.liferay.portal.service.GroupLocalServiceUtil;
034    import com.liferay.portal.util.PortletKeys;
035    
036    import java.io.InputStream;
037    
038    import java.net.URL;
039    
040    import java.nio.CharBuffer;
041    import java.nio.charset.CharsetEncoder;
042    
043    import java.util.List;
044    
045    /**
046     * @author Michael C. Han
047     */
048    public abstract class BaseSpellCheckIndexWriter
049            implements SpellCheckIndexWriter {
050    
051            @Override
052            public void indexQuerySuggestionDictionaries(SearchContext searchContext)
053                    throws SearchException {
054    
055                    try {
056                            for (String languageId : _SUPPORTED_LOCALES) {
057                                    indexKeywords(
058                                            searchContext.getCompanyId(), languageId,
059                                            PropsKeys.INDEX_SEARCH_QUERY_SUGGESTION_DICTIONARY,
060                                            Field.KEYWORD_SEARCH, QUERY_SUGGESTION_TYPE, 0);
061                            }
062                    }
063                    catch (Exception e) {
064                            throw new SearchException(e);
065                    }
066            }
067    
068            @Override
069            public void indexQuerySuggestionDictionary(SearchContext searchContext)
070                    throws SearchException {
071    
072                    try {
073                            indexKeywords(
074                                    searchContext.getCompanyId(), searchContext.getLanguageId(),
075                                    PropsKeys.INDEX_SEARCH_QUERY_SUGGESTION_DICTIONARY,
076                                    Field.KEYWORD_SEARCH, QUERY_SUGGESTION_TYPE, 0);
077                    }
078                    catch (Exception e) {
079                            throw new SearchException(e);
080                    }
081            }
082    
083            @Override
084            public void indexSpellCheckerDictionaries(SearchContext searchContext)
085                    throws SearchException {
086    
087                    try {
088                            for (String languageId : _SUPPORTED_LOCALES) {
089                                    indexKeywords(
090                                            searchContext.getCompanyId(), languageId,
091                                            PropsKeys.INDEX_SEARCH_SPELL_CHECKER_DICTIONARY,
092                                            Field.SPELL_CHECK_WORD, SPELL_CHECKER_TYPE, 0);
093                            }
094                    }
095                    catch (Exception e) {
096                            throw new SearchException(e);
097                    }
098            }
099    
100            @Override
101            public void indexSpellCheckerDictionary(SearchContext searchContext)
102                    throws SearchException {
103    
104                    try {
105                            indexKeywords(
106                                    searchContext.getCompanyId(), searchContext.getLanguageId(),
107                                    PropsKeys.INDEX_SEARCH_SPELL_CHECKER_DICTIONARY,
108                                    Field.SPELL_CHECK_WORD, SPELL_CHECKER_TYPE, 0);
109                    }
110                    catch (Exception e) {
111                            throw new SearchException(e);
112                    }
113            }
114    
115            protected URL getResource(String name) {
116                    ClassLoader contextClassLoader =
117                            Thread.currentThread().getContextClassLoader();
118    
119                    URL url = contextClassLoader.getResource(name);
120    
121                    if (url == null) {
122                            ClassLoader portalClassLoader =
123                                    PortalClassLoaderUtil.getClassLoader();
124    
125                            url = portalClassLoader.getResource(name);
126                    }
127    
128                    return url;
129            }
130    
131            protected String getUID(
132                    long companyId, String languageId, String word, String... parameters) {
133    
134                    StringBundler uidSB = new StringBundler();
135    
136                    uidSB.append(String.valueOf(companyId));
137                    uidSB.append(StringPool.UNDERLINE);
138                    uidSB.append(PortletKeys.SEARCH);
139                    uidSB.append(_PORTLET_SEPARATOR);
140    
141                    int length = 4;
142    
143                    if (parameters != null) {
144                            length += parameters.length;
145                    }
146    
147                    try {
148                            CharsetEncoder charsetEncoder =
149                                    CharsetEncoderUtil.getCharsetEncoder(StringPool.UTF8);
150    
151                            StringBundler keySB = new StringBundler(length);
152    
153                            keySB.append(languageId);
154                            keySB.append(StringPool.UNDERLINE);
155                            keySB.append(word);
156                            keySB.append(StringPool.UNDERLINE);
157    
158                            keySB.append(word.toLowerCase());
159    
160                            if (parameters != null) {
161                                    for (String parameter : parameters) {
162                                            keySB.append(parameter);
163                                            keySB.append(StringPool.UNDERLINE);
164                                    }
165                            }
166    
167                            String key = keySB.toString();
168    
169                            byte[] bytes = DigesterUtil.digestRaw(
170                                    Digester.MD5, charsetEncoder.encode(CharBuffer.wrap(key)));
171    
172                            uidSB.append(Base64.encode(bytes));
173                    }
174                    catch (Exception e) {
175                            throw new IllegalStateException(e);
176                    }
177    
178                    return uidSB.toString();
179            }
180    
181            protected abstract void indexKeywords(
182                            long companyId, long groupId, String languageId,
183                            InputStream inputStream, String keywordFieldName,
184                            String typeFieldValue, int maxNGramLength)
185                    throws Exception;
186    
187            protected void indexKeywords(
188                            long companyId, long groupId, String languageId,
189                            String[] dictionaryFileNames, String keywordFieldName,
190                            String typeFieldValue, int maxNGramLength)
191                    throws Exception {
192    
193                    for (String dictionaryFileName : dictionaryFileNames) {
194                            InputStream inputStream = null;
195    
196                            if (_log.isInfoEnabled()) {
197                                    _log.info(
198                                            "Start indexing dictionary for " + dictionaryFileName);
199                            }
200    
201                            try {
202                                    URL url = getResource(dictionaryFileName);
203    
204                                    if (url == null) {
205                                            if (_log.isWarnEnabled()) {
206                                                    _log.warn("Unable to read " + dictionaryFileName);
207                                            }
208    
209                                            continue;
210                                    }
211    
212                                    inputStream = url.openStream();
213    
214                                    if (inputStream == null) {
215                                            if (_log.isWarnEnabled()) {
216                                                    _log.warn("Unable to read " + dictionaryFileName);
217                                            }
218    
219                                            continue;
220                                    }
221    
222                                    indexKeywords(
223                                            companyId, groupId, languageId, inputStream,
224                                            keywordFieldName, typeFieldValue, maxNGramLength);
225                            }
226                            finally {
227                                    StreamUtil.cleanUp(inputStream);
228                            }
229    
230                            if (_log.isInfoEnabled()) {
231                                    _log.info(
232                                            "Finished indexing dictionary for " + dictionaryFileName);
233                            }
234                    }
235            }
236    
237            protected void indexKeywords(
238                            long companyId, String languageId, String propsKey,
239                            String keywordFieldName, String typeFieldValue, int maxNGramLength)
240                    throws Exception {
241    
242                    String[] dictionaryFileNames = PropsUtil.getArray(
243                            propsKey, new Filter(languageId));
244    
245                    indexKeywords(
246                            companyId, 0, languageId, dictionaryFileNames, keywordFieldName,
247                            typeFieldValue, maxNGramLength);
248    
249                    List<Group> groups = GroupLocalServiceUtil.getLiveGroups();
250    
251                    for (Group group : groups) {
252                            String[] groupDictionaryFileNames = PropsUtil.getArray(
253                                    PropsKeys.INDEX_SEARCH_SPELL_CHECKER_DICTIONARY,
254                                    new Filter(languageId, String.valueOf(group.getGroupId())));
255    
256                            if (Validator.isNull(groupDictionaryFileNames)) {
257                                    continue;
258                            }
259    
260                            indexKeywords(
261                                    companyId, group.getGroupId(), languageId, dictionaryFileNames,
262                                    keywordFieldName, typeFieldValue, maxNGramLength);
263                    }
264            }
265    
266            protected static final String QUERY_SUGGESTION_TYPE = "querySuggestion";
267    
268            protected static final String SPELL_CHECKER_TYPE = "spellChecker";
269    
270            private static final String _PORTLET_SEPARATOR = "_PORTLET_";
271    
272            private static final String[] _SUPPORTED_LOCALES = StringUtil.split(
273                    PropsUtil.get(PropsKeys.INDEX_SEARCH_SPELL_CHECKER_SUPPORTED_LOCALES));
274    
275            private static Log _log = LogFactoryUtil.getLog(
276                    BaseSpellCheckIndexWriter.class);
277    
278    }