com.hp.autonomy.searchcomponents.idol.parametricvalues.IdolParametricValuesServiceImpl Maven / Gradle / Ivy
/*
* Copyright 2015-2018 Micro Focus International plc.
* Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
*/
package com.hp.autonomy.searchcomponents.idol.parametricvalues;
import com.autonomy.aci.client.services.AciErrorException;
import com.autonomy.aci.client.util.AciParameters;
import com.hp.autonomy.aci.content.ranges.DateRange;
import com.hp.autonomy.aci.content.ranges.NumericRange;
import com.hp.autonomy.aci.content.ranges.ParametricFieldRange;
import com.hp.autonomy.aci.content.ranges.ParametricFieldRanges;
import com.hp.autonomy.searchcomponents.core.caching.CacheNames;
import com.hp.autonomy.searchcomponents.core.fields.TagNameFactory;
import com.hp.autonomy.searchcomponents.core.parametricvalues.BucketingParams;
import com.hp.autonomy.searchcomponents.core.parametricvalues.BucketingParamsHelper;
import com.hp.autonomy.searchcomponents.core.parametricvalues.DependentParametricField;
import com.hp.autonomy.searchcomponents.core.parametricvalues.ParametricRequest;
import com.hp.autonomy.searchcomponents.core.parametricvalues.ParametricValuesService;
import com.hp.autonomy.searchcomponents.core.search.QueryRequest;
import com.hp.autonomy.searchcomponents.idol.annotations.IdolService;
import com.hp.autonomy.searchcomponents.idol.fields.IdolFieldsRequestBuilder;
import com.hp.autonomy.searchcomponents.idol.fields.IdolFieldsService;
import com.hp.autonomy.searchcomponents.idol.search.HavenSearchAciParameterHandler;
import com.hp.autonomy.searchcomponents.idol.search.IdolQueryRestrictions;
import com.hp.autonomy.searchcomponents.idol.search.QueryExecutor;
import com.hp.autonomy.types.idol.responses.DateOrNumber;
import com.hp.autonomy.types.idol.responses.FlatField;
import com.hp.autonomy.types.idol.responses.GetQueryTagValuesResponseData;
import com.hp.autonomy.types.idol.responses.RecursiveField;
import com.hp.autonomy.types.idol.responses.TagValue;
import com.hp.autonomy.types.requests.idol.actions.tags.DateRangeInfo;
import com.hp.autonomy.types.requests.idol.actions.tags.DateValueDetails;
import com.hp.autonomy.types.requests.idol.actions.tags.FieldPath;
import com.hp.autonomy.types.requests.idol.actions.tags.NumericRangeInfo;
import com.hp.autonomy.types.requests.idol.actions.tags.NumericValueDetails;
import com.hp.autonomy.types.requests.idol.actions.tags.QueryTagCountInfo;
import com.hp.autonomy.types.requests.idol.actions.tags.QueryTagInfo;
import com.hp.autonomy.types.requests.idol.actions.tags.RangeInfo;
import com.hp.autonomy.types.requests.idol.actions.tags.RangeInfoBuilder;
import com.hp.autonomy.types.requests.idol.actions.tags.RangeInfoValue;
import com.hp.autonomy.types.requests.idol.actions.tags.TagActions;
import com.hp.autonomy.types.requests.idol.actions.tags.TagName;
import com.hp.autonomy.types.requests.idol.actions.tags.ValueDetails;
import com.hp.autonomy.types.requests.idol.actions.tags.ValueDetailsBuilder;
import com.hp.autonomy.types.requests.idol.actions.tags.params.FieldTypeParam;
import com.hp.autonomy.types.requests.idol.actions.tags.params.GetQueryTagValuesParams;
import com.hp.autonomy.types.requests.idol.actions.tags.params.SortParam;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import javax.xml.bind.JAXBElement;
import java.io.Serializable;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static com.hp.autonomy.searchcomponents.core.parametricvalues.ParametricValuesService.PARAMETRIC_VALUES_SERVICE_BEAN_NAME;
/**
* Default Idol implementation of {@link ParametricValuesService}
*/
@Service(PARAMETRIC_VALUES_SERVICE_BEAN_NAME)
@IdolService
class IdolParametricValuesServiceImpl implements IdolParametricValuesService {
static final String VALUE_NODE_NAME = "value";
static final String VALUES_NODE_NAME = "values";
static final String VALUE_MIN_NODE_NAME = "valuemin";
static final String VALUE_MAX_NODE_NAME = "valuemax";
static final String VALUE_AVERAGE_NODE_NAME = "valueaverage";
static final String VALUE_SUM_NODE_NAME = "valuesum";
static final String AFTER_END_OF_RANGE = "after upper end of range";
static final String BEFORE_END_OF_RANGE = "before lower end of range";
private final HavenSearchAciParameterHandler parameterHandler;
private final IdolFieldsService fieldsService;
private final ObjectFactory fieldsRequestBuilderFactory;
private final BucketingParamsHelper bucketingParamsHelper;
private final TagNameFactory tagNameFactory;
private final QueryExecutor queryExecutor;
@SuppressWarnings("ConstructorWithTooManyParameters")
@Autowired
IdolParametricValuesServiceImpl(
final HavenSearchAciParameterHandler parameterHandler,
final IdolFieldsService fieldsService,
final ObjectFactory fieldsRequestBuilderFactory,
final BucketingParamsHelper bucketingParamsHelper,
final TagNameFactory tagNameFactory,
final QueryExecutor queryExecutor
) {
this.parameterHandler = parameterHandler;
this.fieldsService = fieldsService;
this.fieldsRequestBuilderFactory = fieldsRequestBuilderFactory;
this.bucketingParamsHelper = bucketingParamsHelper;
this.tagNameFactory = tagNameFactory;
this.queryExecutor = queryExecutor;
}
@Override
public Set getParametricValues(final IdolParametricRequest parametricRequest) throws AciErrorException {
final Collection fieldNames = new HashSet<>();
fieldNames.addAll(parametricRequest.getFieldNames());
if(fieldNames.isEmpty()) {
fieldNames.addAll(lookupFields());
}
return fieldNames.isEmpty()
? Collections.emptySet()
: getFlatFields(parametricRequest, fieldNames)
.stream()
.map(this::flatFieldToTagInfo)
.filter(queryTagInfo -> !queryTagInfo.getValues().isEmpty())
.collect(Collectors.toCollection(LinkedHashSet::new));
}
@Override
@Cacheable(CacheNames.NUMERIC_PARAMETRIC_VALUES_IN_BUCKETS)
public List getNumericParametricValuesInBuckets(final IdolParametricRequest parametricRequest, final Map> bucketingParamsPerField) throws AciErrorException {
if(parametricRequest.getFieldNames().isEmpty()) {
return Collections.emptyList();
} else {
bucketingParamsHelper.validateBucketingParams(parametricRequest, bucketingParamsPerField);
final Map> boundariesPerField = bucketingParamsPerField
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry -> bucketingParamsHelper.calculateNumericBoundaries(entry.getValue())));
final List ranges = boundariesPerField
.entrySet()
.stream()
.map(entry -> new NumericRange(entry.getKey().getNormalisedPath(), entry.getValue()))
.collect(Collectors.toList());
final IdolParametricRequest bucketingRequest = parametricRequest.toBuilder()
.maxValues(null)
.start(1)
.ranges(ranges)
.sort(SortParam.NumberIncreasing)
.build();
@SuppressWarnings("RedundantTypeArguments") // presumably Java bug
final List results = getFlatFields(bucketingRequest, parametricRequest.getFieldNames()).stream()
.map(this.flatFieldToRangeInfo(boundariesPerField, this::parseNumericRange, NumericRangeInfo::builder, NumericRangeInfo.Value::new))
.collect(Collectors.toList());
return results;
}
}
@Override
@Cacheable(CacheNames.DATE_PARAMETRIC_VALUES_IN_BUCKETS)
public List getDateParametricValuesInBuckets(final IdolParametricRequest parametricRequest, final Map> bucketingParamsPerField) throws AciErrorException {
if(parametricRequest.getFieldNames().isEmpty()) {
return Collections.emptyList();
} else {
bucketingParamsHelper.validateBucketingParams(parametricRequest, bucketingParamsPerField);
final Map> boundariesPerField = bucketingParamsPerField
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry -> bucketingParamsHelper.calculateDateBoundaries(entry.getValue())));
final List ranges = boundariesPerField
.entrySet()
.stream()
.map(entry -> new DateRange(entry.getKey().getNormalisedPath(), entry.getValue()))
.collect(Collectors.toList());
final IdolParametricRequest bucketingRequest = parametricRequest.toBuilder()
.maxValues(null)
.start(1)
.ranges(ranges)
.sort(SortParam.ReverseDate)
.build();
@SuppressWarnings("RedundantTypeArguments") // presumably Java bug
final List results = getFlatFields(bucketingRequest, parametricRequest.getFieldNames())
.stream()
.map(this.flatFieldToRangeInfo(boundariesPerField, this::parseDateRange, DateRangeInfo::builder, DateRangeInfo.Value::new))
.collect(Collectors.toList());
return results;
}
}
@Override
public List getDependentParametricValues(final IdolParametricRequest parametricRequest) throws AciErrorException {
final Collection fieldNames = new ArrayList<>();
fieldNames.addAll(parametricRequest.getFieldNames());
if(fieldNames.isEmpty()) {
fieldNames.addAll(lookupFields());
}
final List results;
if(fieldNames.isEmpty()) {
results = Collections.emptyList();
} else {
final AciParameters aciParameters = createAciParameters(parametricRequest, fieldNames);
aciParameters.add(GetQueryTagValuesParams.FieldDependence.name(), true);
aciParameters.add(GetQueryTagValuesParams.FieldDependenceMultiLevel.name(), true);
final GetQueryTagValuesResponseData responseData = executeAction(parametricRequest, aciParameters);
results = responseData.getField().isEmpty() || responseData.getValues() == null
? Collections.emptyList()
: addDisplayNamesToRecursiveFields(responseData.getValues().getField(), responseData.getField().get(0).getName());
}
return results;
}
@Override
public Map getNumericValueDetails(final IdolParametricRequest parametricRequest) throws AciErrorException {
return getValueDetails(parametricRequest, NumericValueDetails::builder, this::numberFromValueDetailsElement);
}
@Override
public Map getDateValueDetails(final IdolParametricRequest parametricRequest) throws AciErrorException {
return getValueDetails(parametricRequest, DateValueDetails::builder, this::zonedDateTimeFromValueDetailsElement);
}
private , V extends ValueDetails, B extends ValueDetailsBuilder> Map getValueDetails(
final IdolParametricRequest parametricRequest,
final Supplier valueDetailsBuilderSupplier,
final Function, T> parseElement
) {
if(parametricRequest.getFieldNames().isEmpty()) {
return Collections.emptyMap();
} else {
final AciParameters aciParameters = createAciParameters(parametricRequest, parametricRequest.getFieldNames());
aciParameters.add(GetQueryTagValuesParams.MaxValues.name(), 1);
aciParameters.add(GetQueryTagValuesParams.ValueDetails.name(), true);
final GetQueryTagValuesResponseData responseData = executeAction(parametricRequest, aciParameters);
final Collection fields = responseData.getField();
final Map output = new LinkedHashMap<>();
for(final FlatField field : fields) {
final List> valueElements = field.getValueAndSubvalueOrValues();
final B builder = valueDetailsBuilderSupplier.get();
Integer values = null;
for(final JAXBElement> element : valueElements) {
final String elementLocalName = element.getName().getLocalPart();
if(VALUE_MIN_NODE_NAME.equals(elementLocalName)) {
builder.min(parseElement.apply(element));
} else if(VALUE_MAX_NODE_NAME.equals(elementLocalName)) {
builder.max(parseElement.apply(element));
} else if(VALUE_AVERAGE_NODE_NAME.equals(elementLocalName)) {
builder.average(parseElement.apply(element));
} else if(VALUE_SUM_NODE_NAME.equals(elementLocalName)) {
builder.sum((Double)element.getValue());
} else if(VALUES_NODE_NAME.equals(elementLocalName)) {
values = (Integer)element.getValue();
}
}
builder.totalValues(flatFieldTotalValues(field, values));
final FieldPath fieldPath = tagNameFactory.getFieldPath(field.getName().get(0));
output.put(fieldPath, builder.build());
}
return output;
}
}
private AciParameters createAciParameters(final IdolParametricRequest parametricRequest, final Collection fieldNames) {
final AciParameters aciParameters = new AciParameters(TagActions.GetQueryTagValues.name());
parameterHandler.addSearchRestrictions(aciParameters, parametricRequest.getQueryRestrictions());
parameterHandler.addUserIdentifiers(aciParameters);
if(parametricRequest.isModified()) {
parameterHandler.addQmsParameters(aciParameters, parametricRequest.getQueryRestrictions());
}
parameterHandler.addSecurityInfo(aciParameters);
aciParameters.add(GetQueryTagValuesParams.DocumentCount.name(), true);
aciParameters.add(GetQueryTagValuesParams.FieldName.name(), StringUtils.join(fieldNames.stream().map(FieldPath::getNormalisedPath).toArray(String[]::new), ','));
aciParameters.add(GetQueryTagValuesParams.Predict.name(), true);
return aciParameters;
}
private Collection lookupFields() {
return Optional.ofNullable(
fieldsService.getFields(fieldsRequestBuilderFactory.getObject().fieldType(FieldTypeParam.Parametric).build()).get(FieldTypeParam.Parametric)
).orElse(Collections.emptySet()).stream()
.map(TagName::getId)
.collect(Collectors.toList());
}
private & Serializable, D extends Comparable & Serializable, V extends RangeInfoValue, R extends RangeInfo, B extends RangeInfoBuilder>
Function flatFieldToRangeInfo(
final Map> boundariesPerField,
final Function parseValue,
final Supplier builderConstructor,
final RangeInfoValue.Constructor valueConstructor
) {
return flatField -> {
final TagName tagName = tagNameFactory.buildTagName(flatField.getName().get(0));
final List> valueElements = flatField.getValueAndSubvalueOrValues();
int count = 0;
final List values = new LinkedList<>();
for(final JAXBElement> element : valueElements) {
final String elementLocalName = element.getName().getLocalPart();
if(VALUE_NODE_NAME.equals(elementLocalName)) {
final TagValue tagValue = (TagValue)element.getValue();
final T[] minAndMax = parseValue.apply(tagValue);
values.add(valueConstructor.apply(minAndMax[0], minAndMax[1], tagValue.getCount()));
} else if(VALUES_NODE_NAME.equals(elementLocalName)) {
count = (Integer)element.getValue();
}
}
final List boundaries = boundariesPerField.get(tagName.getId());
// If no documents match the query parameters, GetQueryTagValues does not return any buckets
if(values.isEmpty()) {
values.addAll(bucketingParamsHelper.emptyBuckets(boundaries, valueConstructor));
}
// All buckets have the same size, so just use the value from the first one
final D bucketSize = values.get(0).getBucketSize();
return builderConstructor.get()
.id(tagName.getId().getNormalisedPath())
.displayName(tagName.getDisplayName())
.count(count)
.min(boundaries.get(0))
.max(boundaries.get(boundaries.size() - 1))
.bucketSize(bucketSize)
.values(values)
.build();
};
}
private Double[] parseNumericRange(final TagValue tagValue) {
final String[] rangeValues = tagValue.getValue().split(",");
final Double min = Double.valueOf(rangeValues[0]);
final Double max = Double.valueOf(rangeValues[1]);
return new Double[]{min, max};
}
private ZonedDateTime[] parseDateRange(final TagValue tagValue) {
final ZonedDateTime min = ZonedDateTime.parse(tagValue.getDate(), DATE_FORMAT);
final ZonedDateTime max = ZonedDateTime.parse(tagValue.getEndDate(), DATE_FORMAT);
return new ZonedDateTime[]{min, max};
}
private QueryTagInfo flatFieldToTagInfo(final FlatField flatField) {
final String name = flatField.getName().get(0);
final Set values = flatField.getValueAndSubvalueOrValues().stream()
.filter(element -> VALUE_NODE_NAME.equals(element.getName().getLocalPart()))
.map(element -> {
final TagValue tagValue = (TagValue)element.getValue();
final String value = tagValue.getValue();
final String displayValue = tagNameFactory.getTagDisplayValue(name, value);
return new QueryTagCountInfo(value, displayValue, tagValue.getCount());
})
.collect(Collectors.toCollection(LinkedHashSet::new));
final TagName tagName = tagNameFactory.buildTagName(name);
return QueryTagInfo.builder()
.id(tagName.getId().getNormalisedPath())
.displayName(tagName.getDisplayName())
.values(values)
.totalValues(flatFieldTotalValues(flatField, null))
.build();
}
private int flatFieldTotalValues(final FlatField flatField, final Integer values) {
// If no values are returned for a field, IDOL does not return a element
// For (non-parametric) numeric fields we should use values. is missing on a DAH in combine
// mode, or is always 0 if you're using content or a DAH in mirror mode.
// That's why we use the Math.max below.
final Integer totalValues = flatField.getTotalValues();
if (totalValues != null) {
return values != null ? Math.max(totalValues, values) : totalValues;
}
return values != null ? values : 0;
}
private Collection getFlatFields(final IdolParametricRequest parametricRequest, final Collection fieldNames) {
final AciParameters aciParameters = createAciParameters(parametricRequest, fieldNames);
aciParameters.add(GetQueryTagValuesParams.Start.name(), parametricRequest.getStart());
aciParameters.add(GetQueryTagValuesParams.MaxValues.name(), parametricRequest.getMaxValues());
aciParameters.add(GetQueryTagValuesParams.Sort.name(), parametricRequest.getSort());
aciParameters.add(GetQueryTagValuesParams.Ranges.name(), new ParametricFieldRanges(parametricRequest.getRanges()));
aciParameters.add(GetQueryTagValuesParams.ValueDetails.name(), true);
aciParameters.add(GetQueryTagValuesParams.TotalValues.name(), true);
aciParameters.add(GetQueryTagValuesParams.ValueRestriction.name(), String.join(",", parametricRequest.getValueRestrictions()));
final GetQueryTagValuesResponseData responseData = executeAction(parametricRequest, aciParameters);
return responseData.getField();
}
private GetQueryTagValuesResponseData executeAction(final ParametricRequest idolParametricRequest, final AciParameters aciParameters) {
return queryExecutor.executeGetQueryTagValues(
aciParameters,
idolParametricRequest.isModified()
? QueryRequest.QueryType.MODIFIED
: QueryRequest.QueryType.RAW
);
}
private List addDisplayNamesToRecursiveFields(final Collection recursiveFields, final List fieldNames) {
return fieldNames.isEmpty()
? Collections.emptyList()
: recursiveFields.stream()
// We want to ignore fields where the count is missing, e.g. https://jira.autonomy.com/browse/FIND-1496
.filter(a -> a.getCount() != null)
.map(recursiveField -> DependentParametricField.builder()
.value(recursiveField.getValue())
.displayValue(tagNameFactory.getTagDisplayValue(fieldNames.get(0), recursiveField.getValue()))
.count(recursiveField.getCount())
.subFields(
addDisplayNamesToRecursiveFields(
recursiveField.getField(),
fieldNames.subList(1, fieldNames.size())
)
)
.build())
.collect(Collectors.toList());
}
private double numberFromValueDetailsElement(final JAXBElement> element) {
return ((DateOrNumber)element.getValue()).getValue();
}
private ZonedDateTime zonedDateTimeFromValueDetailsElement(final JAXBElement> element) {
final String date = ((DateOrNumber)element.getValue()).getDate();
return date == null
? null
: AFTER_END_OF_RANGE.equals(date) ? null
: BEFORE_END_OF_RANGE.equals(date) ? null
: ZonedDateTime.parse(date, DATE_FORMAT);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy