
com.hp.autonomy.searchcomponents.hod.search.fields.HodSearchResultDeserializer Maven / Gradle / Ivy
/*
* Copyright 2015 Hewlett-Packard Development Company, L.P.
* Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
*/
package com.hp.autonomy.searchcomponents.hod.search.fields;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.hp.autonomy.frontend.configuration.ConfigService;
import com.hp.autonomy.hod.client.api.textindex.query.search.PromotionType;
import com.hp.autonomy.searchcomponents.core.config.FieldInfo;
import com.hp.autonomy.searchcomponents.core.config.FieldType;
import com.hp.autonomy.searchcomponents.core.config.FieldValue;
import com.hp.autonomy.searchcomponents.core.fields.FieldDisplayNameGenerator;
import com.hp.autonomy.searchcomponents.core.fields.FieldPathNormaliser;
import com.hp.autonomy.searchcomponents.core.search.PromotionCategory;
import com.hp.autonomy.searchcomponents.hod.configuration.HodSearchCapable;
import com.hp.autonomy.searchcomponents.hod.search.HodSearchResult;
import com.hp.autonomy.types.requests.idol.actions.tags.FieldPath;
import org.apache.commons.lang.ArrayUtils;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jackson.JsonComponent;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
@JsonComponent
public class HodSearchResultDeserializer extends JsonDeserializer {
/**
* Properties on JSON documents returned from HOD which should not be added to the HodSearchResult fields map. Fields
* that are not JSON arrays do not need to be listed here.
*/
private static final Set IGNORED_PROPERTIES = ImmutableSet.builder()
.add("links")
.build();
private final ConfigService extends HodSearchCapable> configService;
private final FieldDisplayNameGenerator fieldDisplayNameGenerator;
private final FieldPathNormaliser fieldPathNormaliser;
private final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
public HodSearchResultDeserializer(
final ConfigService extends HodSearchCapable> configService,
final FieldDisplayNameGenerator fieldDisplayNameGenerator,
final FieldPathNormaliser fieldPathNormaliser
) {
this.configService = configService;
this.fieldDisplayNameGenerator = fieldDisplayNameGenerator;
this.fieldPathNormaliser = fieldPathNormaliser;
}
@Override
public HodSearchResult deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException {
final Map> fieldConfigByName = configService.getConfig().getFieldsInfo().getFieldConfigByName();
final JsonNode node = jsonParser.getCodec().readTree(jsonParser);
final Iterator> fieldsIterator = node.fields();
final Iterable> entryIterable = () -> fieldsIterator;
final Stream> entryStream = StreamSupport.stream(entryIterable.spliterator(), false);
final Map> fieldMap = entryStream
.filter(entry -> {
return entry.getValue().getNodeType() == JsonNodeType.ARRAY && !IGNORED_PROPERTIES.contains(entry.getKey());
})
.reduce(
ImmutableMap.of(),
(map, entry) -> {
final FieldPath fieldPath = fieldPathNormaliser.normaliseFieldPath(entry.getKey());
final List stringValues = parseNodeAsStringList(entry.getValue());
// Config field info may or may not have a display name and it may contain friendly value names
final Optional> maybeConfigFieldInfo = Optional.ofNullable(fieldConfigByName.get(fieldPath));
// If there is a config entry for this field, we may have seen one with the same ID already
final Optional> maybeExistingFieldInfo = maybeConfigFieldInfo.flatMap(configFieldInfo -> {
return Optional.ofNullable(map.get(configFieldInfo.getId()));
});
final FieldInfo> newFieldInfo = reduceFieldInfo(fieldPath, stringValues, maybeConfigFieldInfo, maybeExistingFieldInfo);
final String id = newFieldInfo.getId();
final ImmutableMap.Builder> builder = ImmutableMap.builder();
builder.put(id, newFieldInfo);
map.entrySet().stream()
.filter(existingEntry -> !existingEntry.getKey().equals(id))
.forEach(builder::put);
return builder.build();
},
this::mergeMaps
);
return HodSearchResult.builder()
.reference(parseAsString(node, "reference"))
.index(parseAsString(node, "index"))
.title(parseAsString(node, "title"))
.summary(parseAsString(node, "summary"))
.weight(parseAsDouble(node, "weight"))
.fieldMap(fieldMap)
.date(parseAsDateFromArray(node, "date"))
.promotionCategory(parsePromotionCategory(node, "promotion"))
.build();
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private FieldInfo> reduceFieldInfo(
final FieldPath fieldPath,
final Collection stringValues,
final Optional> maybeConfigFieldInfo,
final Optional> maybeExistingFieldInfo
) {
if (maybeExistingFieldInfo.isPresent()) {
final FieldInfo> existingFieldInfo = maybeExistingFieldInfo.get();
final String id = existingFieldInfo.getId();
final FieldType fieldType = existingFieldInfo.getType();
return existingFieldInfo.toBuilder()
.name(fieldPath)
.values(parseValues(fieldType, id, stringValues))
.build();
} else {
final String id;
final FieldType fieldType;
final boolean advanced;
if (maybeConfigFieldInfo.isPresent()) {
final FieldInfo> configFieldInfo = maybeConfigFieldInfo.get();
id = configFieldInfo.getId();
advanced = configFieldInfo.isAdvanced();
fieldType = configFieldInfo.getType();
} else {
id = fieldPath.getNormalisedPath();
advanced = true;
fieldType = FieldType.STRING;
}
return FieldInfo.builder()
.id(id)
.name(fieldPath)
.displayName(fieldDisplayNameGenerator.generateDisplayNameFromId(id))
.type(fieldType)
.values(parseValues(fieldType, id, stringValues))
.advanced(advanced)
.build();
}
}
private ImmutableMap mergeMaps(final Map map1, final Map map2) {
return ImmutableMap.builder()
.putAll(map1)
.putAll(map2)
.build();
}
private Collection> parseValues(final FieldType fieldType, final String fieldId, final Collection stringValues) {
return stringValues.stream()
.map(stringValue -> {
@SuppressWarnings("unchecked")
final T value = (T) fieldType.parseValue(fieldType.getType(), stringValue);
final String displayValue = fieldDisplayNameGenerator.generateDisplayValueFromId(fieldId, value, fieldType);
return new FieldValue<>(value, displayValue);
})
.collect(Collectors.toList());
}
private String parseAsString(@SuppressWarnings("TypeMayBeWeakened") final JsonNode node, final String fieldName) throws JsonProcessingException {
final JsonNode jsonNode = node.get(fieldName);
return jsonNode != null ? objectMapper.treeToValue(jsonNode, String.class) : null;
}
private String[] parseAsStringArray(@SuppressWarnings("TypeMayBeWeakened") final JsonNode node, final String fieldName) throws JsonProcessingException {
final JsonNode jsonNode = node.get(fieldName);
return jsonNode != null ? objectMapper.treeToValue(jsonNode, String[].class) : null;
}
private String parseAsStringFromArray(@SuppressWarnings("TypeMayBeWeakened") final JsonNode node, final String fieldName) throws JsonProcessingException {
final String[] values = parseAsStringArray(node, fieldName);
return ArrayUtils.isNotEmpty(values) ? values[0] : null;
}
@SuppressWarnings("SameParameterValue")
private Double parseAsDouble(@SuppressWarnings("TypeMayBeWeakened") final JsonNode node, final String fieldName) throws JsonProcessingException {
final String value = parseAsString(node, fieldName);
return value != null ? Double.parseDouble(value) : null;
}
@SuppressWarnings("SameParameterValue")
private DateTime parseAsDateFromArray(@SuppressWarnings("TypeMayBeWeakened") final JsonNode node, final String fieldName) throws JsonProcessingException {
final String value = parseAsStringFromArray(node, fieldName);
return value != null ? FieldType.DATE.parseValue(DateTime.class, value) : null;
}
private List parseNodeAsStringList(final TreeNode node) {
try {
return Arrays.asList(objectMapper.treeToValue(node, String[].class));
} catch (final JsonProcessingException e) {
throw new IllegalStateException("Failed to parse JSON array", e);
}
}
@SuppressWarnings("SameParameterValue")
private PromotionCategory parsePromotionCategory(@SuppressWarnings("TypeMayBeWeakened") final JsonNode node, final String fieldName) throws JsonProcessingException {
final String value = parseAsString(node, fieldName);
PromotionCategory promotionCategory = null;
if (value != null) {
final PromotionType promotionType = PromotionType.valueOf(value);
switch (promotionType) {
case DYNAMIC_PROMOTION:
case STATIC_REFERENCE_PROMOTION:
promotionCategory = PromotionCategory.SPOTLIGHT;
break;
case STATIC_CONTENT_PROMOTION:
promotionCategory = PromotionCategory.STATIC_CONTENT_PROMOTION;
break;
case CARDINAL_PLACEMENT:
promotionCategory = PromotionCategory.CARDINAL_PLACEMENT;
break;
case NONE:
promotionCategory = PromotionCategory.NONE;
break;
}
}
return promotionCategory;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy