/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library 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 Lesser General Public License for more * details. */ package com.liferay.portal.kernel.search.suggest; import com.liferay.portal.kernel.configuration.Filter; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.model.Group; import com.liferay.portal.kernel.nio.charset.CharsetEncoderUtil; import com.liferay.portal.kernel.search.Field; import com.liferay.portal.kernel.search.SearchContext; import com.liferay.portal.kernel.search.SearchException; import com.liferay.portal.kernel.service.GroupLocalServiceUtil; import com.liferay.portal.kernel.util.ArrayUtil; import com.liferay.portal.kernel.util.Base64; import com.liferay.portal.kernel.util.Digester; import com.liferay.portal.kernel.util.DigesterUtil; import com.liferay.portal.kernel.util.PortalClassLoaderUtil; import com.liferay.portal.kernel.util.PropsKeys; import com.liferay.portal.kernel.util.PropsUtil; import com.liferay.portal.kernel.util.StreamUtil; import com.liferay.portal.kernel.util.StringBundler; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.StringUtil; import java.io.InputStream; import java.net.URL; import java.nio.CharBuffer; import java.nio.charset.CharsetEncoder; import java.util.List; /** * @author Michael C. Han */ public abstract class BaseSpellCheckIndexWriter implements SpellCheckIndexWriter { @Override public void indexKeyword( SearchContext searchContext, float weight, String keywordType) throws SearchException { if (!keywordType.equals(SuggestionConstants.TYPE_QUERY_SUGGESTION) && !keywordType.equals(SuggestionConstants.TYPE_SPELL_CHECKER)) { throw new IllegalArgumentException( "Invalid keyword type " + keywordType); } long groupId = 0; long[] groupIds = searchContext.getGroupIds(); if ((groupIds != null) && (groupIds.length > 0)) { groupId = groupIds[1]; } String keywordFieldName = Field.KEYWORD_SEARCH; String typeFieldValue = SuggestionConstants.TYPE_QUERY_SUGGESTION; int maxNGramLength = _querySuggestionMaxNGramLength; if (keywordType.equals(SuggestionConstants.TYPE_SPELL_CHECKER)) { keywordFieldName = Field.SPELL_CHECK_WORD; typeFieldValue = SuggestionConstants.TYPE_SPELL_CHECKER; maxNGramLength = 0; } try { indexKeyword( searchContext, groupId, searchContext.getLanguageId(), searchContext.getKeywords(), weight, keywordFieldName, typeFieldValue, maxNGramLength); } catch (Exception e) { throw new SearchException(e); } } @Override public void indexQuerySuggestionDictionaries(SearchContext searchContext) throws SearchException { try { for (String languageId : _SUPPORTED_LOCALES) { indexKeywords( searchContext, languageId, PropsKeys.INDEX_SEARCH_QUERY_SUGGESTION_DICTIONARY, Field.KEYWORD_SEARCH, SuggestionConstants.TYPE_QUERY_SUGGESTION, _querySuggestionMaxNGramLength); } } catch (Exception e) { throw new SearchException(e); } } @Override public void indexQuerySuggestionDictionary(SearchContext searchContext) throws SearchException { try { indexKeywords( searchContext, searchContext.getLanguageId(), PropsKeys.INDEX_SEARCH_QUERY_SUGGESTION_DICTIONARY, Field.KEYWORD_SEARCH, SuggestionConstants.TYPE_QUERY_SUGGESTION, _querySuggestionMaxNGramLength); } catch (Exception e) { throw new SearchException(e); } } @Override public void indexSpellCheckerDictionaries(SearchContext searchContext) throws SearchException { try { for (String languageId : _SUPPORTED_LOCALES) { indexKeywords( searchContext, languageId, PropsKeys.INDEX_SEARCH_SPELL_CHECKER_DICTIONARY, Field.SPELL_CHECK_WORD, SuggestionConstants.TYPE_SPELL_CHECKER, 0); } } catch (Exception e) { throw new SearchException(e); } } @Override public void indexSpellCheckerDictionary(SearchContext searchContext) throws SearchException { try { indexKeywords( searchContext, searchContext.getLanguageId(), PropsKeys.INDEX_SEARCH_SPELL_CHECKER_DICTIONARY, Field.SPELL_CHECK_WORD, SuggestionConstants.TYPE_SPELL_CHECKER, 0); } catch (Exception e) { throw new SearchException(e); } } public void setQuerySuggestionMaxNGramLength( int querySuggestionMaxNGramLength) { _querySuggestionMaxNGramLength = querySuggestionMaxNGramLength; } protected URL getResource(String name) { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); URL url = contextClassLoader.getResource(name); if (url == null) { ClassLoader portalClassLoader = PortalClassLoaderUtil.getClassLoader(); url = portalClassLoader.getResource(name); } return url; } protected String getUID( long companyId, String languageId, String word, String... parameters) { StringBundler uidSB = new StringBundler(5); uidSB.append(String.valueOf(companyId)); uidSB.append(StringPool.UNDERLINE); uidSB.append(Field.SPELL_CHECK_WORD); uidSB.append(StringPool.UNDERLINE); int length = 5; if (parameters != null) { length += 2 * parameters.length; } try { CharsetEncoder charsetEncoder = CharsetEncoderUtil.getCharsetEncoder(StringPool.UTF8); StringBundler keySB = new StringBundler(length); keySB.append(languageId); keySB.append(StringPool.UNDERLINE); keySB.append(word); keySB.append(StringPool.UNDERLINE); keySB.append(StringUtil.toLowerCase(word)); if (parameters != null) { for (String parameter : parameters) { keySB.append(parameter); keySB.append(StringPool.UNDERLINE); } } String key = keySB.toString(); byte[] bytes = DigesterUtil.digestRaw( Digester.MD5, charsetEncoder.encode(CharBuffer.wrap(key))); uidSB.append(Base64.encode(bytes)); } catch (Exception e) { throw new IllegalStateException(e); } return uidSB.toString(); } protected abstract void indexKeyword( SearchContext searchContext, long groupId, String languageId, String keyword, float weight, String keywordFieldName, String typeFieldValue, int maxNGramLength) throws Exception; protected abstract void indexKeywords( SearchContext searchContext, long groupId, String languageId, InputStream inputStream, String keywordFieldName, String typeFieldValue, int maxNGramLength) throws Exception; protected void indexKeywords( SearchContext searchContext, long groupId, String languageId, String[] dictionaryFileNames, String keywordFieldName, String typeFieldValue, int maxNGramLength) throws Exception { for (String dictionaryFileName : dictionaryFileNames) { InputStream inputStream = null; if (_log.isInfoEnabled()) { _log.info( "Start indexing dictionary for " + dictionaryFileName); } try { URL url = getResource(dictionaryFileName); if (url == null) { if (_log.isWarnEnabled()) { _log.warn("Unable to read " + dictionaryFileName); } continue; } inputStream = url.openStream(); if (inputStream == null) { if (_log.isWarnEnabled()) { _log.warn("Unable to read " + dictionaryFileName); } continue; } indexKeywords( searchContext, groupId, languageId, inputStream, keywordFieldName, typeFieldValue, maxNGramLength); } finally { StreamUtil.cleanUp(inputStream); } if (_log.isInfoEnabled()) { _log.info( "Finished indexing dictionary for " + dictionaryFileName); } } } protected void indexKeywords( SearchContext searchContext, String languageId, String propsKey, String keywordFieldName, String typeFieldValue, int maxNGramLength) throws Exception { String[] dictionaryFileNames = PropsUtil.getArray( propsKey, new Filter(languageId)); indexKeywords( searchContext, 0, languageId, dictionaryFileNames, keywordFieldName, typeFieldValue, maxNGramLength); List<Group> groups = GroupLocalServiceUtil.getLiveGroups(); for (Group group : groups) { String[] groupDictionaryFileNames = PropsUtil.getArray( propsKey, new Filter(languageId, String.valueOf(group.getGroupId()))); if (ArrayUtil.isEmpty(groupDictionaryFileNames)) { continue; } indexKeywords( searchContext, group.getGroupId(), languageId, groupDictionaryFileNames, keywordFieldName, typeFieldValue, maxNGramLength); } } private static final String[] _SUPPORTED_LOCALES = StringUtil.split( PropsUtil.get(PropsKeys.INDEX_SEARCH_SPELL_CHECKER_SUPPORTED_LOCALES)); private static final Log _log = LogFactoryUtil.getLog( BaseSpellCheckIndexWriter.class); private int _querySuggestionMaxNGramLength = 50; }