/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you 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.elasticsearch.index.query.support;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.join.BitSetProducer;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.mapper.object.ObjectMapper;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.query.QueryParsingException;
import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException;
/**
* A helper that helps with parsing inner queries of the nested query.
* 1) Takes into account that type nested path can appear before or after the inner query
* 2) Updates the {@link NestedScope} when parsing the inner query.
*/
public class NestedInnerQueryParseSupport {
protected final QueryParseContext parseContext;
private BytesReference source;
private Query innerQuery;
private Query innerFilter;
protected String path;
private boolean filterParsed = false;
private boolean queryParsed = false;
protected boolean queryFound = false;
protected boolean filterFound = false;
protected BitSetProducer parentFilter;
protected Query childFilter;
protected ObjectMapper nestedObjectMapper;
private ObjectMapper parentObjectMapper;
public NestedInnerQueryParseSupport(XContentParser parser, SearchContext searchContext) {
parseContext = searchContext.queryParserService().getParseContext();
parseContext.reset(parser);
}
public NestedInnerQueryParseSupport(QueryParseContext parseContext) {
this.parseContext = parseContext;
}
public void query() throws IOException {
if (path != null) {
setPathLevel();
try {
innerQuery = parseContext.parseInnerQuery();
} finally {
resetPathLevel();
}
queryParsed = true;
} else {
source = XContentFactory.smileBuilder().copyCurrentStructure(parseContext.parser()).bytes();
}
queryFound = true;
}
public void filter() throws IOException {
if (path != null) {
setPathLevel();
try {
innerFilter = parseContext.parseInnerFilter();
} finally {
resetPathLevel();
}
filterParsed = true;
} else {
source = XContentFactory.smileBuilder().copyCurrentStructure(parseContext.parser()).bytes();
}
filterFound = true;
}
public Query getInnerQuery() throws IOException {
if (queryParsed) {
return innerQuery;
} else {
if (path == null) {
throw new QueryParsingException(parseContext, "[nested] requires 'path' field");
}
if (!queryFound) {
throw new QueryParsingException(parseContext, "[nested] requires either 'query' or 'filter' field");
}
XContentParser old = parseContext.parser();
try {
XContentParser innerParser = XContentHelper.createParser(source);
parseContext.parser(innerParser);
setPathLevel();
try {
innerQuery = parseContext.parseInnerQuery();
} finally {
resetPathLevel();
}
queryParsed = true;
return innerQuery;
} finally {
parseContext.parser(old);
}
}
}
public Query getInnerFilter() throws IOException {
if (filterParsed) {
return innerFilter;
} else {
if (path == null) {
throw new QueryParsingException(parseContext, "[nested] requires 'path' field");
}
if (!filterFound) {
throw new QueryParsingException(parseContext, "[nested] requires either 'query' or 'filter' field");
}
setPathLevel();
XContentParser old = parseContext.parser();
try {
XContentParser innerParser = XContentHelper.createParser(source);
parseContext.parser(innerParser);
innerFilter = parseContext.parseInnerFilter();
filterParsed = true;
return innerFilter;
} finally {
resetPathLevel();
parseContext.parser(old);
}
}
}
public void setPath(String path) {
this.path = path;
nestedObjectMapper = parseContext.getObjectMapper(path);
if (nestedObjectMapper == null) {
throw new QueryParsingException(parseContext, "[nested] failed to find nested object under path [" + path + "]");
}
if (!nestedObjectMapper.nested().isNested()) {
throw new QueryParsingException(parseContext, "[nested] nested object under path [" + path + "] is not of nested type");
}
}
public String getPath() {
return path;
}
public ObjectMapper getNestedObjectMapper() {
return nestedObjectMapper;
}
public boolean queryFound() {
return queryFound;
}
public boolean filterFound() {
return filterFound;
}
public ObjectMapper getParentObjectMapper() {
return parentObjectMapper;
}
private void setPathLevel() {
ObjectMapper objectMapper = parseContext.nestedScope().getObjectMapper();
if (objectMapper == null) {
parentFilter = parseContext.bitsetFilter(Queries.newNonNestedFilter());
} else {
parentFilter = parseContext.bitsetFilter(objectMapper.nestedTypeFilter());
}
childFilter = nestedObjectMapper.nestedTypeFilter();
parentObjectMapper = parseContext.nestedScope().nextLevel(nestedObjectMapper);
}
private void resetPathLevel() {
parseContext.nestedScope().previousLevel();
}
}