/* * Copyright 2010 Outerthought bvba * * 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 org.lilyproject.indexer.model.indexerconf; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; import org.lilyproject.repository.api.HierarchyPath; import org.lilyproject.repository.api.LRepository; import org.lilyproject.repository.api.QName; import org.lilyproject.repository.api.Record; import org.lilyproject.repository.api.RepositoryException; import org.lilyproject.repository.api.TypeManager; import org.lilyproject.repository.api.ValueType; /** * A default implementation of a formatter than is able to treat any value. */ public class DefaultFormatter implements Formatter { private static final Map<String, ValueFormatter> FORMATTERS; static { FORMATTERS = new HashMap<String, ValueFormatter>(); FORMATTERS.put("LIST", new ListFormatter()); FORMATTERS.put("PATH", new PathFormatter()); FORMATTERS.put("RECORD", new RecordFormatter()); FORMATTERS.put("DATE", new DateFormatter()); FORMATTERS.put("DATETIME", new DateTimeFormatter()); } private static final ValueFormatter ALL_FORMATTER = new AllFormatter(); @Override public List<String> format(List<IndexValue> indexValues, LRepository repository) throws InterruptedException { List<String> results = new ArrayList<String>(); for (IndexValue value : filterValues(indexValues)) { FormatContext formatCtx = new FormatContext(repository); ValueType valueType = value.fieldType.getValueType(); if (valueType.getBaseName().equals("LIST")) { // The values of the first list-level are supplied as individual IndexValues valueType = valueType.getNestedValueType(); } String result = formatCtx.format(value.value, valueType, formatCtx); if (result != null) { results.add(result); } else { results.addAll(formatCtx.results); } } return results; } protected List<IndexValue> filterValues(List<IndexValue> indexValues) { return indexValues; } protected ValueFormatter getFormatter(ValueType valueType) { ValueFormatter formatter = FORMATTERS.get(valueType.getBaseName()); return formatter == null ? ALL_FORMATTER : formatter; } public class FormatContext implements ValueFormatter { /** * This stack can be useful for formatters that want to behave differently depending on * how they are nested in other types. For example in case of nested lists, you might * want to format each nesting level differently. */ Deque<ValueType> valueTypeStack = new ArrayDeque<ValueType>(); LRepository repository; List<String> results = new ArrayList<String>(); public FormatContext(LRepository repository) { this.repository = repository; } @Override public String format(Object value, ValueType valueType, FormatContext formatCtx) throws InterruptedException { valueTypeStack.push(valueType); String result = getFormatter(valueType).format(value, valueType, this); valueTypeStack.pop(); return result; } } public static interface ValueFormatter { /** * This method has the choice of either returning a formatted value, or appending it to the supplied * results list. Appending it to the results list has the effect of sending a multi-value to the indexer * (so the index field needs to support multiple values), while returning a string will cause the value * to be used by the parent formatter. */ public String format(Object value, ValueType valueType, FormatContext formatCtx) throws InterruptedException; } public static void appendNonNull(StringBuilder builder, String text) { if (text != null) { builder.append(text); } } public static String returnBuilderResult(StringBuilder builder) { return builder.length() > 0 ? builder.toString() : null; } protected static class ListFormatter implements ValueFormatter { @Override public String format(Object list, ValueType valueType, FormatContext formatCtx) throws InterruptedException { StringBuilder builder = new StringBuilder(); for (Object value : (List)list) { String formatted = formatCtx.format(value, valueType.getNestedValueType(), formatCtx); // separate the values by a space if (builder.length() > 0) { builder.append(" "); } builder.append(formatted); } return returnBuilderResult(builder); } } protected static class PathFormatter implements ValueFormatter { @Override public String format(Object path, ValueType valueType, FormatContext formatCtx) throws InterruptedException { StringBuilder builder = new StringBuilder(); for (Object value : (HierarchyPath)path) { String formatted = formatCtx.format(value, valueType.getNestedValueType(), formatCtx); builder.append("/"); builder.append(formatted); } return returnBuilderResult(builder); } } protected static class RecordFormatter implements ValueFormatter { @Override public String format(Object record, ValueType valueType, FormatContext formatCtx) throws InterruptedException { StringBuilder builder = new StringBuilder(); TypeManager typeManager = formatCtx.repository.getTypeManager(); for (Map.Entry<QName, Object> field : ((Record)record).getFields().entrySet()) { ValueType fieldValueType; try { fieldValueType = typeManager.getFieldTypeByName(field.getKey()).getValueType(); } catch (RepositoryException e) { // error loading field type: skip this field continue; } String result = formatCtx.format(field.getValue(), fieldValueType, formatCtx); if (result != null) { if (builder.length() > 0) { builder.append(" "); } builder.append(result); } } return returnBuilderResult(builder); } } protected static class DateFormatter implements ValueFormatter { @Override public String format(Object value, ValueType valueType, FormatContext formatCtx) throws InterruptedException { LocalDate date = (LocalDate)value; return date.toDateTimeAtStartOfDay(DateTimeZone.UTC).toString() + "/DAY"; } } protected static class DateTimeFormatter implements ValueFormatter { @Override public String format(Object value, ValueType valueType, FormatContext formatCtx) throws InterruptedException { DateTime dateTime = (DateTime)value; return dateTime.toDateTime(DateTimeZone.UTC).toString(); } } protected static class AllFormatter implements ValueFormatter { @Override public String format(Object value, ValueType valueType, FormatContext formatCtx) throws InterruptedException { return value.toString(); } } }