it seems that SpringData ES don't provide classes to fetch highlights returned by ES. Spring Data can return Lists of Objects but the highlights sections in the Json returned by ES is in a separated part that is not handled by the "ElasticSearchTemplate" class.
Code example :-
QueryBuilder query = QueryBuilders.matchQuery("name","tom");
SearchQuery searchQuery =new NativeSearchQueryBuilder().withQuery(query).
with HighlightFields(new Field("name")).build();
List<ESDocument> publications = elasticsearchTemplate.queryForList
(searchQuery, ESDocument.class);
I might be wrong, but I can't figure out to do only with SpringDataES. Someone can post an example of how we can get highlights with Spring Data ES ?
Thanks in advance !
From the test cases in spring data elasticsearch I've found solution to this :
This can be helpful.
#Test
public void shouldReturnHighlightedFieldsForGivenQueryAndFields() {
//given
String documentId = randomNumeric(5);
String actualMessage = "some test message";
String highlightedMessage = "some <em>test</em> message";
SampleEntity sampleEntity = SampleEntity.builder().id(documentId)
.message(actualMessage)
.version(System.currentTimeMillis()).build();
IndexQuery indexQuery = getIndexQuery(sampleEntity);
elasticsearchTemplate.index(indexQuery);
elasticsearchTemplate.refresh(SampleEntity.class);
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(termQuery("message", "test"))
.withHighlightFields(new HighlightBuilder.Field("message"))
.build();
Page<SampleEntity> sampleEntities = elasticsearchTemplate.queryForPage(searchQuery, SampleEntity.class, new SearchResultMapper() {
#Override
public <T> Page<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
List<SampleEntity> chunk = new ArrayList<SampleEntity>();
for (SearchHit searchHit : response.getHits()) {
if (response.getHits().getHits().length <= 0) {
return null;
}
SampleEntity user = new SampleEntity();
user.setId(searchHit.getId());
user.setMessage((String) searchHit.getSource().get("message"));
user.setHighlightedMessage(searchHit.getHighlightFields().get("message").fragments()[0].toString());
chunk.add(user);
}
if (chunk.size() > 0) {
return new PageImpl<T>((List<T>) chunk);
}
return null;
}
});
assertThat(sampleEntities.getContent().get(0).getHighlightedMessage(), is(highlightedMessage));
}
Spring Data Elasticsearch 4.0 now has the SearchPage result type, which makes things a little easier if we need to return highlighted results:
This is a working sample:
String query = "(id:123 OR id:456) AND (database:UCLF) AND (services:(sealer?), services:electronic*)"
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withPageable(pageable)
.withQuery(queryStringQuery(query))
.withSourceFilter(sourceFilter)
.withHighlightFields(new HighlightBuilder.Field("goodsAndServices"))
.build();
SearchHits<Trademark> searchHits = template.search(searchQuery, Trademark.class, IndexCoordinates.of("trademark"));
SearchPage<Trademark> page = SearchHitSupport.searchPageFor(searchHits, searchQuery.getPageable());
return (Page<Trademark>) SearchHitSupport.unwrapSearchHits(page);
And this would be the response from Page object in json:
{
"content": [
{
"id": "123",
"score": 12.10748,
"sortValues": [],
"content": {
"_id": "1P0XzXIBdRyrchmFplEA",
"trademarkIdentifier": "abc234",
"goodsAndServices": null,
"language": "EN",
"niceClass": "2",
"sequence": null,
"database": "UCLF",
"taggedResult": null
},
"highlightFields": {
"goodsAndServices": [
"VARNISHES, <em>SEALERS</em>, AND NATURAL WOOD FINISHES"
]
}
}
],
"pageable": {
"sort": {
"unsorted": true,
"sorted": false,
"empty": true
},
"offset": 0,
"pageNumber": 0,
"pageSize": 20,
"unpaged": false,
"paged": true
},
"searchHits": {
"totalHits": 1,
"totalHitsRelation": "EQUAL_TO",
"maxScore": 12.10748,
"scrollId": null,
"searchHits": [
{
"id": "123",
"score": 12.10748,
"sortValues": [],
"content": {
"_id": "1P0XzXIBdRyrchmFplEA",
"trademarkIdentifier": "abc234",
"goodsAndServices": null,
"language": "EN",
"niceClass": "2",
"sequence": null,
"database": "UCLF",
"taggedResult": null
},
"highlightFields": {
"goodsAndServices": [
"VARNISHES, <em>SEALERS</em>, AND NATURAL WOOD FINISHES"
]
}
}
],
"aggregations": null,
"empty": false
},
"totalPages": 1,
"totalElements": 1,
"size": 20,
"number": 0,
"numberOfElements": 1,
"last": true,
"first": true,
"sort": {
"unsorted": true,
"sorted": false,
"empty": true
},
"empty": false
}
Actually, you could do the following, with a custom ResultExtractor:
QueryBuilder query = QueryBuilders.matchQuery("name", "tom");
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(query)
.withHighlightFields(new Field("name")).build();
return elasticsearchTemplate.query(searchQuery.build(), new CustomResultExtractor());
And then
public class CustomResultExtractor implements ResultsExtractor<List<MyClass>> {
private final DefaultEntityMapper defaultEntityMapper;
public CustomResultExtractor() {
defaultEntityMapper = new DefaultEntityMapper();
}
#Override
public List<MyClass> extract(SearchResponse response) {
return StreamSupport.stream(response.getHits().spliterator(), false)
.map(this::searchHitToMyClass)
.collect(Collectors.toList());
}
private MyClass searchHitToMyClass(SearchHit searchHit) {
MyElasticSearchObject myObject;
try {
myObject = defaultEntityMapper.mapToObject(searchHit.getSourceAsString(), MyElasticSearchObject.class);
} catch (IOException e) {
throw new ElasticsearchException("failed to map source [ " + searchHit.getSourceAsString() + "] to class " + MyElasticSearchObject.class.getSimpleName(), e);
}
List<String> highlights = searchHit.getHighlightFields().values()
.stream()
.flatMap(highlightField -> Arrays.stream(highlightField.fragments()))
.map(Text::string)
.collect(Collectors.toList());
// Or whatever you want to do with the highlights
return new MyClass(myObject, highlights);
}}
Note that I used a list but you could use any other iterable data structure. Also, you could do something else with the highlights. Here I'm simply listing them.
https://stackoverflow.com/a/37163711/6643675
The first answer does works,but I found some pageable problems with its returned result,which display with the wrong total elements and toalpages.Arter I checkout the DefaultResultMapper implementation, the returned statement shoud be return new AggregatedPageImpl((List<T>) chunk, pageable, totalHits, response.getAggregations(), response.getScrollId(), maxScore);,and then it works with paging.wish i could help you guys~
Related
I have built a controller which returns a list of objects
fun getUserCommunicationSettings(
#PathVariable commId: String,
#CommunicationTypeConstraint #RequestParam(required = false) commType: CommunicationType?
): ResponseEntity<out UserCommResponse> {
return communicationSettingsService.getUserCommSettings(commType, commId)
.mapError { mapUserCommErrorToResponse(it) }
.map { ResponseEntity.ok(SuccessResponse(it)) }
.fold<ResponseEntity<SuccessResponse<List<CommunicationSettingsDto>>>, ResponseEntity<ErrorResponse>, ResponseEntity<out UserCommResponse>>(
{ it },
{ it },
)
}
problem is it returns the following json with "entity" tag which i'd like to get rid of
{
"entity": [
{
"userId": "1075",
"userType": "CUSTOMER",
"communicationId": "972547784682",
"communicationType": "CALL",
"messageType": "CALL_COLLECTION"
}
]
}
any ideas?
I'm trying to add or update a nested object in Elasticsearch using a script. Below script works fine if integrations is already an array, but when it is null, below script throws a null pointer exception. How do I initialize ctx._source.integrations to be an empty array if its value is null? (Something like the equivalent of JavaScript's myObject.integrations = myObject.integrations ?? [])
POST /products/_update/VFfrnQrKlC5bwdfdeaQ7
{
"script": {
"source": """
ctx._source.integrations.removeIf(i -> i.id == params.integration.id);
ctx._source.integrations.add(params.integration);
ctx._source.integrationCount = ctx._source.integrations.length;
""",
"params": {
"integration": {
"id": "dVTV8GjHj8pXFnlYUUlI",
"from": true,
"to": false,
"vendor": "sfeZWDpZXlF5Qa8mUsiF",
"targetProduct": {
"id": "nyILhphvCrGYm53cfaOx",
"name": "Test Product",
"categoryIds": []
}
}
}
}
}
ok i think this does the trick:
if (ctx._source.integrations == null) {
ctx._source.integrations = new ArrayList();
}
is there a short hand to this like in the JS example?
I want to pass kendo grid DataSourceRequest to web api
my web api iS:
[HttpPost]
public HttpResponseMessage GetAll([FromBody] DataSourceRequest request)
{
try
{
var itemList = new JsonListFormat<ItemVm>
{
Data = new ItemCrud().GetItemList(request),
Total = new ItemCrud().GetItemTotalCount()
};
return Request.CreateResponse(HttpStatusCode.OK, itemList);
}
catch (Exception ex)
{
return HttpResponseController.HttpResponseException(Request, ex);
}
}
but request.Filters is always null.
for test I call my web api method with postman and this json data:
{
"page": 10,
"pageSize": 20,
"sorts": [
{
"member": "Title",
"sortDirection": 0
}
],
"filters": [
{
"convertedValue": "test",
"member": "Title",
"memberType": null,
"operator": 2,
"value": "test"
}
],
"groups": null,
"aggregates": []
}
everything pass to request parameter but rquest.Filters is null !!!
anyone can explain what's my problem.
thanks
Did you try this option?
dataSource.serverFiltering = true;
please check Server filtering
Make a simple RestController
#RestController
public class Controloler
#Value
class MyData {
int value;
}
#GetMapping(value = "/datas", produces = MediaTypes.HAL_JSON_VALUE)
public PagedResources<Resource<MyData>> getMyData(PagedResourcesAssembler<MyData> assembler,
#RequestParam(required = false) String param,
#PageableDefault Pageable pageRequest)
{
MyData data = new MyData(1);
Page<MyData> page = new PageImpl<>(Collections.singletonList(data), pageRequest, 100);
Link selfLink = linkTo(methodOn(Controloler.class).getMyData(assembler, param, pageRequest)).withSelfRel();
return assembler.toResource(page, selfLink);
}
}
When I try to get page curl "http://localhost:8080/datas?param=12&page=2" have a problem with self link generation
{
"_embedded": {
"myDataList": [
{
"value": 1
}
]
},
"_links": {
"first": {
"href": "http://localhost:8080/datas?param=12&page=0&size=10"
},
"prev": {
"href": "http://localhost:8080/datas?param=12&page=1&size=10"
},
"self": {
"href": "http://localhost:8080/datas?param=12"
},
"next": {
"href": "http://localhost:8080/datas?param=12&page=3&size=10"
},
"last": {
"href": "http://localhost:8080/datas?param=12&page=9&size=10"
}
},
"page": {
"size": 10,
"totalElements": 100,
"totalPages": 10,
"number": 2
}
}
In my opinion, self link should be http://localhost:8080/datas?param=12&page=2&size=10.
Just now I can solve this problem without using pageable in arguments, just exact params page and size. But, I hope there is some solution with pageable
I've seen that in case of spring-data-rest self have a type of template. But I'd like to get the url I've requested
In my opinion, self link should be http://localhost:8080/datas?param=12&page=2&size=10.
I agree. In fact, it seems to be a bug. The most recent version of PagedResourcesAssembler does it differently:
Link selfLink = link.map(it -> it.withSelfRel())//
.orElseGet(() -> createLink(base, page.getPageable(), Link.REL_SELF));
(source)
Buggy versions of that class are doing this:
resources.add(createLink(base, null, Link.REL_SELF));
The createLink method is never passed the needed Pageable, but null as the second argument.
So, if you can't upgrade to the most recent version you can still work-around it:
Link selfLink = linkTo(methodOn(Controloler.class).getMyData(assembler, param, pageRequest)).withSelfRel();
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(selfLink.expand().getHref());
new HateoasPageableHandlerMethodArgumentResolver().enhance(builder, null, pageRequest);
Link newSelfLink = new Link(builder.build().toString());
According to Oliver's comment in an issue opened to address this, the self link should not contain template information, and this is not a bug.
I want to use conditional query.
Here is my query
db.projects.aggregate([
{
"$group": {
"_id": "$iecode",
"treatmentArms": { "$first": "$evaluationDTOList" }
}
},
{ "$unwind": "$treatmentArms" },
{
"$group": {
"_id": null,
"Package": {
"$sum": {
"$cond": [
{ "$eq": [ "$treatmentArms.mechanismOrPkg", "Package" ] },
1, 0
]
}
},
"Constraint-relaxing mechanisms": {
"$sum": {
"$cond": [
{
"$and": [
{ "$eq": [ "$treatmentArms.mechanismOrPkg", "Mechanism" ] },
{ "$eq": [ "$treatmentArms.mechanismTested1", "Constraint-relaxing mechanisms" ] }
]
},
1,
0 ]
}
},
"Delivery mechanisms": {
"$sum": {
"$cond": [
{
"$and": [
{ "$eq": [ "$treatmentArms.mechanismOrPkg", "Mechanism" ] },
{ "$eq": [ "$treatmentArms.mechanismTested1", "Delivery mechanisms" ] }
]
},
1,
0 ]
}
},
"Other": {
"$sum": {
"$cond": [
{
"$and": [
{ "$eq": [ "$treatmentArms.mechanismOrPkg", "Mechanism" ] },
{ "$eq": [ "$treatmentArms.mechanismTested1", "Other" ] }
]
},
1,
0 ]
}
}
}
}
])
Here is my java code
DBObject groupByIECode = new BasicDBObject("$group",
new BasicDBObject("_id", new BasicDBObject("iecode","$iecode")).append("treatmentArms",new BasicDBObject("$first","$evaluationDTOList")));
System.out.println("groupByIECode: "+groupByIECode.toString());
DBObject unwind = new BasicDBObject("$unwind","$treatmentArms");
System.out.println("unwind: "+unwind.toString());
DBObject finalCalculation = new BasicDBObject("$group",new BasicDBObject("_id",null))
.append(
"Package", new BasicDBObject(
"$sum", new BasicDBObject(
"$cond", new Object[]{
new BasicDBObject(
"$eq", new Object[]{ "$treatmentArms.mechanismOrPkg", "Package"}
),
1,
0
}
)
)
);
System.out.println("finalCalculation: "+finalCalculation);
final AggregationOutput output = projects.aggregate(match,groupByIECode,unwind,finalCalculation);
It gives me MongoException$DuplicateKey
Later I found out that $cond operator is not supported in spring mongotemplate. So how do I implememt this conditional query with spring mongotemplate.
This link has some explanation but it do not shows full implementation
From the documentation, a canonical example for using the Spring Data MongoDB support for the MongoDB Aggregation Framework looks as follows:
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
Aggregation agg = newAggregation(
pipelineOP1(),
pipelineOP2(),
pipelineOPn()
);
AggregationResults<OutputType> results = mongoTemplate.aggregate(agg,
"INPUT_COLLECTION_NAME", OutputType.class);
List<OutputType> mappedResult = results.getMappedResults();
Note that if you provide an input class as the first parameter to the
newAggregation method the MongoTemplate will derive the name of the
input collection from this class. Otherwise if you don’t specify
an input class you must provide the name of the input collection
explicitly. If an input-class and an input-collection is provided the
latter takes precedence.
For your query, create a workaround that implements the AggregationOperation interface to take in a DBObject that represents a single group operation in an aggregation pipeline with the $cond operator:
public class GroupAggregationOperation implements AggregationOperation {
private DBObject operation;
public GroupAggregationOperation (DBObject operation) {
this.operation = operation;
}
#Override
public DBObject toDBObject(AggregationOperationContext context) {
return context.getMappedObject(operation);
}
}
Then implement the $group operation as a DBObject in the aggregation pipeline that is the same as the one you have:
DBObject operation = (DBObject) new BasicDBObject("$group", new BasicDBObject("_id", null))
.append(
"Package", new BasicDBObject(
"$sum", new BasicDBObject(
"$cond", new Object[]{
new BasicDBObject(
"$eq", new Object[]{ "$treatmentArms.mechanismOrPkg", "Package"}
),
1,
0
}
)
)
);
which you can then use as:
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
GroupAggregationOperation groupOp = new GroupAggregationOperation(operation);
Aggregation agg = newAggregation(
group("iecode").first("treatmentArms").as("treatmentArms"),
unwind("treatmentArms"),
groupOp
);
AggregationResults<Entity> results = mongoTemplate.aggregate(agg, Entity.class);
List<Entity> entities = results.getMappedResults();