/** * 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; import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayInputStream; import com.liferay.portal.kernel.search.geolocation.GeoLocationPoint; import com.liferay.portal.kernel.util.ArrayUtil; import com.liferay.portal.kernel.util.DateFormatFactoryUtil; import com.liferay.portal.kernel.util.FastDateFormatFactoryUtil; import com.liferay.portal.kernel.util.FileUtil; import com.liferay.portal.kernel.util.GetterUtil; import com.liferay.portal.kernel.util.LocaleUtil; import com.liferay.portal.kernel.util.LocalizationUtil; import com.liferay.portal.kernel.util.PropsKeys; import com.liferay.portal.kernel.util.PropsUtil; import com.liferay.portal.kernel.util.SetUtil; import com.liferay.portal.kernel.util.StringBundler; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.kernel.util.Validator; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.text.DateFormat; import java.text.Format; import java.text.ParseException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Set; /** * @author Brian Wing Shun Chan * @author Bruno Farache */ public class DocumentImpl implements Document { public static String getLocalizedName(Locale locale, String name) { if (locale == null) { return name; } String languageId = LocaleUtil.toLanguageId(locale); return getLocalizedName(languageId, name); } public static String getLocalizedName(String languageId, String name) { return LocalizationUtil.getLocalizedName(name, languageId); } public static String getSortableFieldName(String name) { return name.concat(StringPool.UNDERLINE).concat(_SORTABLE_FIELD_SUFFIX); } public static String getSortFieldName(Sort sort, String scoreFieldName) { if (sort.getType() == Sort.SCORE_TYPE) { return scoreFieldName; } String fieldName = sort.getFieldName(); if (isSortableFieldName(fieldName)) { return fieldName; } if ((sort.getType() == Sort.STRING_TYPE) && !isSortableTextField(fieldName)) { return scoreFieldName; } return getSortableFieldName(fieldName); } public static boolean isSortableFieldName(String name) { return name.endsWith(_SORTABLE_FIELD_SUFFIX); } public static boolean isSortableTextField(String name) { return _defaultSortableTextFields.contains(name); } @Override public void add(Field field) { _fields.put(field.getName(), field); } @Override public void addDate(String name, Date value) { if (value == null) { return; } addDate(name, new Date[] {value}); } @Override public void addDate(String name, Date[] values) { if (values == null) { return; } if (_dateFormat == null) { _dateFormat = FastDateFormatFactoryUtil.getSimpleDateFormat( _INDEX_DATE_FORMAT_PATTERN); } String[] datesString = new String[values.length]; Long[] datesTime = new Long[values.length]; for (int i = 0; i < values.length; i++) { datesString[i] = _dateFormat.format(values[i]); datesTime[i] = values[i].getTime(); } createSortableNumericField(name, false, datesTime); Field field = createField(name, datesString); field.setDates(values); } @Override public void addDateSortable(String name, Date value) { if (value == null) { return; } addDateSortable(name, new Date[] {value}); } @Override public void addDateSortable(String name, Date[] values) { if (values == null) { return; } String[] datesString = new String[values.length]; Long[] datesTime = new Long[values.length]; for (int i = 0; i < values.length; i++) { datesString[i] = _dateFormat.format(values[i]); datesTime[i] = values[i].getTime(); } createSortableNumericField(name, true, datesTime); addKeyword(name, datesString); } @Override public void addFile(String name, byte[] bytes, String fileExt) { InputStream is = new UnsyncByteArrayInputStream(bytes); addFile(name, is, fileExt); } @Override public void addFile(String name, File file, String fileExt) throws IOException { InputStream is = new FileInputStream(file); addFile(name, is, fileExt); } @Override public void addFile(String name, InputStream is, String fileExt) { addText(name, FileUtil.extractText(is, fileExt)); } @Override public void addFile( String name, InputStream is, String fileExt, int maxStringLength) { addText(name, FileUtil.extractText(is, fileExt, maxStringLength)); } @Override public void addGeoLocation(double latitude, double longitude) { addGeoLocation(Field.GEO_LOCATION, latitude, longitude); } @Override public void addGeoLocation(String name, double latitude, double longitude) { Field field = new Field(name); field.setGeoLocationPoint(new GeoLocationPoint(latitude, longitude)); add(field); } @Override public void addKeyword(String name, boolean value) { addKeyword(name, String.valueOf(value)); } @Override public void addKeyword(String name, Boolean value) { addKeyword(name, String.valueOf(value)); } @Override public void addKeyword(String name, boolean[] values) { if (values == null) { return; } addKeyword(name, ArrayUtil.toStringArray(values)); } @Override public void addKeyword(String name, Boolean[] values) { if (values == null) { return; } addKeyword(name, ArrayUtil.toStringArray(values)); } @Override public void addKeyword(String name, double value) { addKeyword(name, String.valueOf(value)); } @Override public void addKeyword(String name, Double value) { addKeyword(name, String.valueOf(value)); } @Override public void addKeyword(String name, double[] values) { if (values == null) { return; } addKeyword(name, ArrayUtil.toStringArray(values)); } @Override public void addKeyword(String name, Double[] values) { if (values == null) { return; } addKeyword(name, ArrayUtil.toStringArray(values)); } @Override public void addKeyword(String name, float value) { addKeyword(name, String.valueOf(value)); } @Override public void addKeyword(String name, Float value) { addKeyword(name, String.valueOf(value)); } @Override public void addKeyword(String name, float[] values) { if (values == null) { return; } addKeyword(name, ArrayUtil.toStringArray(values)); } @Override public void addKeyword(String name, Float[] values) { if (values == null) { return; } addKeyword(name, ArrayUtil.toStringArray(values)); } @Override public void addKeyword(String name, int value) { addKeyword(name, String.valueOf(value)); } @Override public void addKeyword(String name, int[] values) { if (values == null) { return; } addKeyword(name, ArrayUtil.toStringArray(values)); } @Override public void addKeyword(String name, Integer value) { addKeyword(name, String.valueOf(value)); } @Override public void addKeyword(String name, Integer[] values) { if (values == null) { return; } addKeyword(name, ArrayUtil.toStringArray(values)); } @Override public void addKeyword(String name, long value) { addKeyword(name, String.valueOf(value)); } @Override public void addKeyword(String name, Long value) { addKeyword(name, String.valueOf(value)); } @Override public void addKeyword(String name, long[] values) { if (values == null) { return; } addKeyword(name, ArrayUtil.toStringArray(values)); } @Override public void addKeyword(String name, Long[] values) { if (values == null) { return; } addKeyword(name, ArrayUtil.toStringArray(values)); } @Override public void addKeyword(String name, short value) { addKeyword(name, String.valueOf(value)); } @Override public void addKeyword(String name, Short value) { addKeyword(name, String.valueOf(value)); } @Override public void addKeyword(String name, short[] values) { if (values == null) { return; } addKeyword(name, ArrayUtil.toStringArray(values)); } @Override public void addKeyword(String name, Short[] values) { if (values == null) { return; } addKeyword(name, ArrayUtil.toStringArray(values)); } @Override public void addKeyword(String name, String value) { addKeyword(name, value, false); } @Override public void addKeyword(String name, String value, boolean lowerCase) { createKeywordField(name, value, lowerCase); createSortableKeywordField(name, value); } @Override public void addKeyword(String name, String[] values) { if (values == null) { return; } createField(name, values); } @Override public void addKeywordSortable(String name, Boolean value) { String valueString = String.valueOf(value); createKeywordField(name, valueString, false); createSortableTextField(name, valueString); } @Override public void addKeywordSortable(String name, Boolean[] values) { if (values == null) { return; } String[] valuesString = ArrayUtil.toStringArray(values); createField(name, valuesString); createSortableTextField(name, valuesString); } @Override public void addKeywordSortable(String name, String value) { createKeywordField(name, value, false); createSortableTextField(name, value); } @Override public void addKeywordSortable(String name, String[] values) { createField(name, values); createSortableTextField(name, values); } @Override public void addLocalizedKeyword(String name, Map<Locale, String> values) { addLocalizedKeyword(name, values, false); } @Override public void addLocalizedKeyword( String name, Map<Locale, String> values, boolean lowerCase) { if ((values == null) || values.isEmpty()) { return; } if (lowerCase) { Map<Locale, String> lowerCaseValues = new HashMap<>(values.size()); for (Map.Entry<Locale, String> entry : values.entrySet()) { String value = GetterUtil.getString(entry.getValue()); lowerCaseValues.put( entry.getKey(), StringUtil.toLowerCase(value)); } values = lowerCaseValues; } createField(name, values); } @Override public void addLocalizedKeyword( String name, Map<Locale, String> values, boolean lowerCase, boolean sortable) { if ((values == null) || values.isEmpty()) { return; } if (lowerCase) { Map<Locale, String> lowerCaseValues = new HashMap<>(values.size()); for (Map.Entry<Locale, String> entry : values.entrySet()) { String value = GetterUtil.getString(entry.getValue()); lowerCaseValues.put( entry.getKey(), StringUtil.toLowerCase(value)); } values = lowerCaseValues; } createField(name, values, sortable); } @Override public void addLocalizedText(String name, Map<Locale, String> values) { if ((values == null) || values.isEmpty()) { return; } Field field = createField(name, values); field.setTokenized(true); } @Override public void addNumber(String name, BigDecimal value) { createNumberField(name, value); } @Override public void addNumber(String name, BigDecimal[] values) { createNumberField(name, values); } @Override public void addNumber(String name, double value) { createNumberField(name, Double.valueOf(value)); } @Override public void addNumber(String name, Double value) { createNumberField(name, value); } @Override public void addNumber(String name, double[] values) { if (values == null) { return; } createNumberField(name, ArrayUtil.toArray(values)); } @Override public void addNumber(String name, Double[] values) { createNumberField(name, values); } @Override public void addNumber(String name, float value) { createNumberField(name, Float.valueOf(value)); } @Override public void addNumber(String name, Float value) { createNumberField(name, value); } @Override public void addNumber(String name, float[] values) { if (values == null) { return; } createNumberField(name, ArrayUtil.toArray(values)); } @Override public void addNumber(String name, Float[] values) { createNumberField(name, values); } @Override public void addNumber(String name, int value) { createNumberField(name, Integer.valueOf(value)); } @Override public void addNumber(String name, int[] values) { if (values == null) { return; } createNumberField(name, ArrayUtil.toArray(values)); } @Override public void addNumber(String name, Integer value) { createNumberField(name, value); } @Override public void addNumber(String name, Integer[] values) { createNumberField(name, values); } @Override public void addNumber(String name, long value) { createNumberField(name, Long.valueOf(value)); } @Override public void addNumber(String name, Long value) { createNumberField(name, value); } @Override public void addNumber(String name, long[] values) { if (values == null) { return; } createNumberField(name, ArrayUtil.toArray(values)); } @Override public void addNumber(String name, Long[] values) { createNumberField(name, values); } @Override public void addNumber(String name, String value) { createNumberField(name, Long.valueOf(value)); } @Override public void addNumber(String name, String[] values) { if (values == null) { return; } Long[] longs = new Long[values.length]; for (int i = 0; i < values.length; i++) { longs[i] = Long.valueOf(values[i]); } createNumberField(name, longs); } @Override public void addNumberSortable(String name, BigDecimal value) { createNumberFieldWithTypedSortable(name, value); } @Override public void addNumberSortable(String name, BigDecimal[] values) { createNumberFieldWithTypedSortable(name, values); } @Override public void addNumberSortable(String name, Double value) { createNumberFieldWithTypedSortable(name, value); } @Override public void addNumberSortable(String name, Double[] values) { createNumberFieldWithTypedSortable(name, values); } @Override public void addNumberSortable(String name, Float value) { createNumberFieldWithTypedSortable(name, value); } @Override public void addNumberSortable(String name, Float[] values) { createNumberFieldWithTypedSortable(name, values); } @Override public void addNumberSortable(String name, Integer value) { createNumberFieldWithTypedSortable(name, value); } @Override public void addNumberSortable(String name, Integer[] values) { createNumberFieldWithTypedSortable(name, values); } @Override public void addNumberSortable(String name, Long value) { createNumberFieldWithTypedSortable(name, value); } @Override public void addNumberSortable(String name, Long[] values) { createNumberFieldWithTypedSortable(name, values); } @Override public void addText(String name, String value) { if (Validator.isNull(value)) { return; } Field field = createField(name, value); field.setTokenized(true); createSortableKeywordField(name, value); } @Override public void addText(String name, String[] values) { if (values == null) { return; } Field field = createField(name, values); field.setTokenized(true); createSortableKeywordField(name, values); } @Override public void addTextSortable(String name, String value) { if (Validator.isNull(value)) { return; } Field field = createField(name, value); field.setTokenized(true); createSortableTextField(name, value); } @Override public void addTextSortable(String name, String[] values) { if (values == null) { return; } Field field = createField(name, values); field.setTokenized(true); createSortableTextField(name, values); } @Override public void addUID(String portletId, long field1) { addUID(portletId, String.valueOf(field1)); } @Override public void addUID(String portletId, long field1, String field2) { addUID(portletId, String.valueOf(field1), field2); } @Override public void addUID(String portletId, Long field1) { addUID(portletId, field1.longValue()); } @Override public void addUID(String portletId, Long field1, String field2) { addUID(portletId, field1.longValue(), field2); } @Override public void addUID(String portletId, String field1) { addUID(portletId, field1, null); } @Override public void addUID(String portletId, String field1, String field2) { addUID(portletId, field1, field2, null); } @Override public void addUID( String portletId, String field1, String field2, String field3) { addUID(portletId, field1, field2, field3, null); } @Override public void addUID( String portletId, String field1, String field2, String field3, String field4) { String uid = portletId + _UID_PORTLET + field1; if (field2 != null) { uid += _UID_FIELD + field2; } if (field3 != null) { uid += _UID_FIELD + field3; } if (field4 != null) { uid += _UID_FIELD + field4; } addKeyword(Field.UID, uid); } @Override public Object clone() { DocumentImpl documentImpl = new DocumentImpl(); documentImpl.setSortableTextFields(_sortableTextFields); return documentImpl; } @Override public String get(Locale locale, String name) { if (locale == null) { return get(name); } String localizedName = getLocalizedName(locale, name); Field field = getField(localizedName); if (field == null) { field = getField(name); } if (field == null) { return StringPool.BLANK; } return field.getValue(); } @Override public String get(Locale locale, String name, String defaultName) { if (locale == null) { return get(name, defaultName); } String localizedName = getLocalizedName(locale, name); Field field = getField(localizedName); if (field == null) { localizedName = getLocalizedName(locale, defaultName); field = getField(localizedName); } if (field == null) { return StringPool.BLANK; } return field.getValue(); } @Override public String get(String name) { Field field = getField(name); if (field == null) { return StringPool.BLANK; } return field.getValue(); } @Override public String get(String name, String defaultName) { Field field = getField(name); if (field == null) { return get(defaultName); } return field.getValue(); } @Override public Date getDate(String name) throws ParseException { DateFormat dateFormat = DateFormatFactoryUtil.getSimpleDateFormat( _INDEX_DATE_FORMAT_PATTERN); return dateFormat.parse(get(name)); } @Override public Field getField(String name) { return doGetField(name, false); } @Override public Map<String, Field> getFields() { return _fields; } @Override public String getPortletId() { String uid = getUID(); int pos = uid.indexOf(_UID_PORTLET); return uid.substring(0, pos); } @Override public String getUID() { Field field = getField(Field.UID); if (field == null) { throw new RuntimeException("UID is not set"); } return field.getValue(); } @Override public String[] getValues(String name) { Field field = getField(name); if (field == null) { return new String[] {StringPool.BLANK}; } return field.getValues(); } @Override public boolean hasField(String name) { if (_fields.containsKey(name)) { return true; } return false; } @Override public boolean isDocumentSortableTextField(String name) { return _sortableTextFields.contains(name); } @Override public void remove(String name) { _fields.remove(name); } public void setFields(Map<String, Field> fields) { _fields = fields; } @Override public void setSortableTextFields(String[] sortableTextFields) { _sortableTextFields = SetUtil.fromArray(sortableTextFields); } @Override public String toString() { StringBundler sb = new StringBundler(5 * _fields.size()); toString(sb, _fields.values()); return sb.toString(); } protected Field createField(String name) { return doGetField(name, true); } protected Field createField( String name, boolean sortable, String... values) { Field field = createField(name); field.setSortable(sortable); field.setValues(values); return field; } protected Field createField( String name, Map<Locale, String> localizedValues) { return createField(name, localizedValues, false); } protected Field createField( String name, Map<Locale, String> localizedValues, boolean sortable) { Field field = createField(name); field.setLocalizedValues(localizedValues); field.setSortable(sortable); return field; } protected Field createField(String name, String... values) { return createField(name, false, values); } protected void createKeywordField( String name, String value, boolean lowerCase) { if (lowerCase && Validator.isNotNull(value)) { value = StringUtil.toLowerCase(value); } createField(name, value); } protected void createNumberField( String name, boolean typify, Number value) { if (value == null) { return; } String valueString = String.valueOf(value); createSortableNumericField(name, typify, valueString, value.getClass()); createField(name, valueString); } protected <T extends Number & Comparable<? super T>> void createNumberField( String name, boolean typify, T... values) { if (values == null) { return; } createSortableNumericField(name, typify, values); createField(name, ArrayUtil.toStringArray(values)); } protected void createNumberField(String name, Number value) { createNumberField(name, false, value); } protected <T extends Number & Comparable<? super T>> void createNumberField( String name, T... values) { createNumberField(name, false, values); } protected void createNumberFieldWithTypedSortable( String name, Number value) { createNumberField(name, true, value); } protected <T extends Number & Comparable<? super T>> void createNumberFieldWithTypedSortable(String name, T... values) { createNumberField(name, true, values); } protected void createSortableKeywordField(String name, String value) { if (isDocumentSortableTextField(name)) { createSortableTextField(name, value); } } protected void createSortableKeywordField(String name, String[] values) { if (isDocumentSortableTextField(name)) { createSortableTextField(name, values); } } protected void createSortableNumericField( String name, boolean typify, String value, Class<? extends Number> clazz) { if (typify) { name = name.concat(StringPool.UNDERLINE).concat("Number"); } Field field = createField(getSortableFieldName(name), value); field.setNumeric(true); field.setNumericClass(clazz); } protected <T extends Number & Comparable<? super T>> void createSortableNumericField(String name, boolean typify, T... values) { if ((values == null) || (values.length == 0)) { return; } T minValue = Collections.min(Arrays.asList(values)); createSortableNumericField( name, typify, String.valueOf(minValue), minValue.getClass()); } protected void createSortableTextField(String name, String value) { String truncatedValue = value; if (value.length() > _SORTABLE_TEXT_FIELDS_TRUNCATED_LENGTH) { truncatedValue = value.substring( 0, _SORTABLE_TEXT_FIELDS_TRUNCATED_LENGTH); } createKeywordField(getSortableFieldName(name), truncatedValue, true); } protected void createSortableTextField(String name, String[] values) { if (values.length == 0) { return; } createSortableTextField( name, Collections.min(Arrays.<String>asList(values))); } protected Field doGetField(String name, boolean createIfNew) { Field field = _fields.get(name); if ((field == null) && createIfNew) { field = new Field(name); _fields.put(name, field); } return field; } protected void setSortableTextFields(Set<String> sortableTextFields) { _sortableTextFields = sortableTextFields; } protected void toString(StringBundler sb, Collection<Field> fields) { sb.append(StringPool.OPEN_CURLY_BRACE); boolean firstField = true; for (Field field : fields) { if (!firstField) { sb.append(StringPool.COMMA); sb.append(StringPool.SPACE); } else { firstField = false; } if (field.hasChildren()) { sb.append(field.getName()); sb.append(StringPool.COLON); toString(sb, field.getFields()); } else { sb.append(field.getName()); sb.append(StringPool.EQUAL); sb.append(Arrays.toString(field.getValues())); } } sb.append(StringPool.CLOSE_CURLY_BRACE); } private static final String _INDEX_DATE_FORMAT_PATTERN = PropsUtil.get( PropsKeys.INDEX_DATE_FORMAT_PATTERN); private static final String _SORTABLE_FIELD_SUFFIX = "sortable"; private static final int _SORTABLE_TEXT_FIELDS_TRUNCATED_LENGTH = GetterUtil.getInteger( PropsUtil.get( PropsKeys.INDEX_SORTABLE_TEXT_FIELDS_TRUNCATED_LENGTH)); private static final String _UID_FIELD = "_FIELD_"; private static final String _UID_PORTLET = "_PORTLET_"; private static Format _dateFormat; private static final Set<String> _defaultSortableTextFields = SetUtil.fromArray( PropsUtil.getArray(PropsKeys.INDEX_SORTABLE_TEXT_FIELDS)); private Map<String, Field> _fields = new HashMap<>(); private Set<String> _sortableTextFields = _defaultSortableTextFields; }