public static final String NESTED_PATH_SEPARATOR = ".";
 public static <T> T throwNotImplemented(String msg) throws ODataApplicationException {
    throw new ODataApplicationException(msg, HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(),
            Locale.ROOT);
}
 @AllArgsConstructor
public class ChildMember extends BaseMember {

    private final String childType;
    private final QueryBuilder query;

    @Override
    public ExpressionResult any() throws ODataApplicationException {
        return new ExpressionResult(hasChildQuery(childType, query, ScoreMode.None));
    }

} @AllArgsConstructor
@Getter
public class ExpressionResult extends BaseMember {

    private final QueryBuilder queryBuilder;

    @Override
    public ExpressionResult and(ExpressionMember expressionMember)
            throws ODataApplicationException {
        return new ExpressionResult(boolQuery().must(queryBuilder)
                .must(((ExpressionResult) expressionMember).getQueryBuilder()));
    }

    @Override
    public ExpressionResult or(ExpressionMember expressionMember) throws ODataApplicationException {
        return new ExpressionResult(boolQuery().should(queryBuilder)
                .should(((ExpressionResult) expressionMember).getQueryBuilder()));
    }

    @Override
    public ExpressionResult not() throws ODataApplicationException {
        return new ExpressionResult(boolQuery().mustNot(queryBuilder));
    }
} @AllArgsConstructor
public class NestedMember extends BaseMember {

    private final String nestedPath;
    private final QueryBuilder query;

    @Override
    public ExpressionResult any() throws ODataApplicationException {
        return new ExpressionResult(nestedQuery(nestedPath, query, ScoreMode.None));
    }

} public class ParentWrapperMember extends ParentMember {

    private QueryBuilder query;

    /**
     * Initialize fields.
     * 
     * @param parentTypes
     *            list of parent type names
     * @param query
     *            inner query
     */
    public ParentWrapperMember(List<String> parentTypes, QueryBuilder query) {
        super(parentTypes);
        this.query = query;
    }

    @Override
    public ExpressionResult any() throws ODataApplicationException {
        return buildParentQuery(query);
    }

} public class ParentPrimitiveMember extends ParentMember {

    private PrimitiveMember primitiveMember;

    /**
     * Initialize fields.
     * 
     * @param parentTypes
     *            list of parent type names
     * @param primitiveMember
     *            primitive member instance
     */
    public ParentPrimitiveMember(List<String> parentTypes, PrimitiveMember primitiveMember) {
        super(parentTypes);
        this.primitiveMember = primitiveMember;
    }

    @Override
    public ExpressionResult eq(ExpressionMember expressionMember) throws ODataApplicationException {
        ExpressionResult expressionResult = primitiveMember.eq(expressionMember);
        return buildParentQuery(expressionResult.getQueryBuilder());
    }

    @Override
    public ExpressionResult ne(ExpressionMember expressionMember) throws ODataApplicationException {
        ExpressionResult expressionResult = primitiveMember.ne(expressionMember);
        return buildParentQuery(expressionResult.getQueryBuilder());
    }

    @Override
    public ExpressionResult ge(ExpressionMember expressionMember) throws ODataApplicationException {
        ExpressionResult expressionResult = primitiveMember.ge(expressionMember);
        return buildParentQuery(expressionResult.getQueryBuilder());
    }

    @Override
    public ExpressionResult gt(ExpressionMember expressionMember) throws ODataApplicationException {
        ExpressionResult expressionResult = primitiveMember.gt(expressionMember);
        return buildParentQuery(expressionResult.getQueryBuilder());
    }

    @Override
    public ExpressionResult le(ExpressionMember expressionMember) throws ODataApplicationException {
        ExpressionResult expressionResult = primitiveMember.le(expressionMember);
        return buildParentQuery(expressionResult.getQueryBuilder());
    }

    @Override
    public ExpressionResult lt(ExpressionMember expressionMember) throws ODataApplicationException {
        ExpressionResult expressionResult = primitiveMember.lt(expressionMember);
        return buildParentQuery(expressionResult.getQueryBuilder());
    }

    @Override
    public ExpressionResult contains(ExpressionMember right) {
        ExpressionResult expressionResult = primitiveMember.contains(right);
        return buildParentQuery(expressionResult.getQueryBuilder());
    }

    @Override
    public ExpressionResult startsWith(ExpressionMember right) {
        ExpressionResult expressionResult = primitiveMember.startsWith(right);
        return buildParentQuery(expressionResult.getQueryBuilder());
    }

    @Override
    public ExpressionResult endsWith(ExpressionMember right) {
        ExpressionResult expressionResult = primitiveMember.endsWith(right);
        return buildParentQuery(expressionResult.getQueryBuilder());
    }

    @Override
    public ExpressionMember date() {
        // Elasticsearch doesn't distinguish between search by the date and
        // search by the timestamp, so no conversion is needed
        return this;
    }

    public PrimitiveMember getPrimitiveMember() {
        return primitiveMember;
    }

}
 public class PrimitiveMember extends AnnotatedMember {

    /**
     * Initialize fields.
     * 
     * @param field
     *            field name
     * @param annotations
     *            annotations list
     */
    public PrimitiveMember(String field, List<EdmAnnotation> annotations) {
        super(field, annotations);
    }

    @Override
    public ExpressionResult eq(ExpressionMember expressionMember) throws ODataApplicationException {
        return new ExpressionResult(getEqQuery(expressionMember));
    }

    @Override
    public ExpressionResult ne(ExpressionMember expressionMember) throws ODataApplicationException {
        return new ExpressionResult(boolQuery().mustNot(getEqQuery(expressionMember)));
    }

    @Override
    public ExpressionResult ge(ExpressionMember expressionMember) throws ODataApplicationException {
        return new ExpressionResult(
                rangeQuery(getField()).gte(((LiteralMember) expressionMember).getValue()));
    }

    @Override
    public ExpressionResult gt(ExpressionMember expressionMember) throws ODataApplicationException {
        return new ExpressionResult(
                rangeQuery(getField()).gt(((LiteralMember) expressionMember).getValue()));
    }

    @Override
    public ExpressionResult le(ExpressionMember expressionMember) throws ODataApplicationException {
        return new ExpressionResult(
                rangeQuery(getField()).lte(((LiteralMember) expressionMember).getValue()));
    }

    @Override
    public ExpressionResult lt(ExpressionMember expressionMember) throws ODataApplicationException {
        return new ExpressionResult(
                rangeQuery(getField()).lt(((LiteralMember) expressionMember).getValue()));
    }

    @Override
    public ExpressionResult contains(ExpressionMember right) {
        LiteralMember literal = (LiteralMember) right;
        return new ExpressionResult(buildContainsQuery(this, literal.getValue()));
    }

    @Override
    public ExpressionResult startsWith(ExpressionMember right) {
        LiteralMember literal = (LiteralMember) right;
        return new ExpressionResult(buildStartsWithQuery(this, (String) literal.getValue()));
    }

    @Override
    public ExpressionResult endsWith(ExpressionMember right) {
        LiteralMember literal = (LiteralMember) right;
        return new ExpressionResult(buildEndsWithQuery(this, (String) literal.getValue()));
    }

    @Override
    public ExpressionMember date() {
        // Elasticsearch doesn't distinguish between search by the date and
        // search by the timestamp, so no conversion is needed
        return this;
    }
} public class ElasticEdmEntityType extends EdmEntityTypeImpl {

    private ElasticCsdlEntityType csdlEntityType;
    private Map<String, ElasticEdmProperty> propertiesCash;
    private Map<String, ElasticEdmNavigationProperty> navigationPropertiesCash;

    /**
     * Constructor to initialize entity type.
     * 
     * @param edm
     *            EDM provider
     * @param name
     *            entity type FQN
     * @param entityType
     *            CSDL entity type
     */
    public ElasticEdmEntityType(Edm edm, FullQualifiedName name, ElasticCsdlEntityType entityType) {
        super(edm, name, entityType);
        this.csdlEntityType = entityType;
    }

    /**
     * Get's index name in Elasticsearch.
     * 
     * @return index name
     */
    public String getESIndex() {
        return csdlEntityType.getESIndex();
    }

    /**
     * Get's type name in Elasticsearch.
     * 
     * @return type name
     */
    public String getESType() {
        return csdlEntityType.getESType();
    }

    /**
     * Get's CSDL property name by Elasticsearch field name.
     * 
     * @param esFieldName
     *            Elasticsearch field name
     * @return found property
     */
    public ElasticEdmProperty findPropertyByEField(String esFieldName) {
        for (Entry<String, ElasticEdmProperty> entry : getESProperties().entrySet()) {
            ElasticEdmProperty property = entry.getValue();
            if (property.getEField().equals(esFieldName)) {
                return property;
            }
        }
        return null;
    }

    /**
     * Gets Elasticsearch properties map.
     * 
     * @return properties map, with key - property name and value -
     *         {@link ElasticEdmProperty} instance
     */
    public Map<String, ElasticEdmProperty> getESProperties() {
        if (propertiesCash == null) {
            Map<String, ElasticEdmProperty> localPorperties = new LinkedHashMap<>();
            List<CsdlProperty> typeProperties = csdlEntityType.getProperties();
            for (CsdlProperty property : typeProperties) {
                if (property instanceof ElasticCsdlProperty) {
                    localPorperties.put(property.getName(),
                            new ElasticEdmProperty(edm, (ElasticCsdlProperty) property));
                }
            }
            propertiesCash = Collections.unmodifiableMap(localPorperties);
        }
        return propertiesCash;
    }

    @Override
    public Map<String, EdmProperty> getProperties() {
        Map<String, EdmProperty> properties = new HashMap<>();
        for (Entry<String, ElasticEdmProperty> entry : getESProperties().entrySet()) {
            properties.put(entry.getKey(), entry.getValue());
        }
        return properties;
    }

    /**
     * Gets Elasticsearch navigation properties map.
     * 
     * @return properties map, with key - property name and value -
     *         {@link ElasticEdmNavigationProperty} instance
     */
    public Map<String, ElasticEdmNavigationProperty> getESNavigationProperties() {
        if (navigationPropertiesCash == null) {
            Map<String, ElasticEdmNavigationProperty> localNavigationProperties = new LinkedHashMap<>();
            List<CsdlNavigationProperty> structuredTypeNavigationProperties = csdlEntityType
                    .getNavigationProperties();
            for (CsdlNavigationProperty property : structuredTypeNavigationProperties) {
                if (property instanceof ElasticCsdlNavigationProperty) {
                    localNavigationProperties.put(property.getName(),
                            new ElasticEdmNavigationProperty(edm,
                                    (ElasticCsdlNavigationProperty) property));
                }
            }
            navigationPropertiesCash = Collections.unmodifiableMap(localNavigationProperties);
        }
        return navigationPropertiesCash;
    }

    @Override
    public Map<String, EdmNavigationProperty> getNavigationProperties() {
        Map<String, EdmNavigationProperty> navigationProperties = new HashMap<>();
        for (Entry<String, ElasticEdmNavigationProperty> entry : getESNavigationProperties()
                .entrySet()) {
            navigationProperties.put(entry.getKey(), entry.getValue());
        }
        return navigationProperties;
    }

} public class ElasticEdmProperty extends EdmPropertyImpl {

    private ElasticCsdlProperty csdlProperty;

    /**
     * Constructor to initialize property.
     * 
     * @param edm
     *            EDM provider
     * @param property
     *            CSDL property
     */
    public ElasticEdmProperty(Edm edm, ElasticCsdlProperty property) {
        super(edm, property);
        this.csdlProperty = property;
    }

    /**
     * Get's field name in Elasticsearch.
     * 
     * @return field name
     */
    public String getEField() {
        return csdlProperty.getESField();
    }
}
import static com.hevelian.olastic.core.elastic.ElasticConstants.NESTED_PATH_SEPARATOR;
import static com.hevelian.olastic.core.utils.ProcessorUtils.throwNotImplemented;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.olingo.commons.api.edm.EdmAnnotation;
import org.apache.olingo.commons.api.edm.EdmEntityType;
import org.apache.olingo.commons.api.edm.EdmProperty;
import org.apache.olingo.commons.api.edm.constants.EdmTypeKind;
import org.apache.olingo.server.api.ODataApplicationException;
import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.UriResourceComplexProperty;
import org.apache.olingo.server.api.uri.UriResourceKind;
import org.apache.olingo.server.api.uri.UriResourceLambdaAll;
import org.apache.olingo.server.api.uri.UriResourceLambdaAny;
import org.apache.olingo.server.api.uri.UriResourceLambdaVariable;
import org.apache.olingo.server.api.uri.UriResourceNavigation;
import org.apache.olingo.server.api.uri.UriResourcePartTyped;
import org.apache.olingo.server.api.uri.UriResourcePrimitiveProperty;
import org.apache.olingo.server.api.uri.UriResourceProperty;
import org.apache.olingo.server.api.uri.queryoption.expression.Binary;
import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitException;
import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitor;
import org.apache.olingo.server.api.uri.queryoption.expression.Member;
import org.apache.olingo.server.api.uri.queryoption.expression.Method;
import org.apache.olingo.server.core.uri.UriInfoImpl;
import com.hevelian.olastic.core.api.uri.queryoption.expression.member.impl.ChildMember;
import com.hevelian.olastic.core.api.uri.queryoption.expression.member.impl.ExpressionResult;
import com.hevelian.olastic.core.api.uri.queryoption.expression.member.impl.NestedMember;
import com.hevelian.olastic.core.api.uri.queryoption.expression.member.impl.ParentWrapperMember;
import com.hevelian.olastic.core.api.uri.queryoption.expression.member.impl.ParentPrimitiveMember;
import com.hevelian.olastic.core.api.uri.queryoption.expression.member.impl.PrimitiveMember;
import com.hevelian.olastic.core.edm.ElasticEdmEntityType;
import com.hevelian.olastic.core.edm.ElasticEdmProperty;
package com.hevelian.olastic.core.api.uri.queryoption.expression.member;





/**
 * Processes raw olingo expression member data.
 * 
 * @author Taras Kohut
 * @author rdidyk
 */
public class MemberHandler {

    private UriResource firstPart;
    private UriResource lastPart;
    private List<UriResource> resourceParts;
    /**
     * represents the path to current member. Is useful in lambdas, because
     * member contains no information about its parent members
     */
    private String pathToMember;
    private Map<String, UriResource> collectionResourceCache;
    private ExpressionVisitor<?> visitor;

    /**
     * Initializes member handler using raw olingo expression member.
     *
     * @param member
     *            raw olingo expression member
     * @param visitor
     *            visitor instance
     */
    public MemberHandler(Member member, ExpressionVisitor<?> visitor) {
        this.visitor = visitor;
        UriInfoImpl resource = (UriInfoImpl) member.getResourcePath();
        resourceParts = resource.getUriResourceParts();
        firstPart = resourceParts.get(0);
        lastPart = resourceParts.get(resourceParts.size() - 1);
        String parentPath = resource.getFragment();
        pathToMember = collectPathToMember(parentPath);
    }

    /**
     * Collects path to member. This path is helpful for complex lambdas, like
     * this one: $filter=info/pages/any(p:p/words/any(w:w eq 'word')) We need to
     * store this path manually because Member inside lambda doesn't contain
     * full path to itself.
     *
     * @param parentPath
     *            path to parent member
     * @return path to current member
     */
    private String collectPathToMember(String parentPath) {
        String parentPathPrefix = parentPath != null ? parentPath : "";
        List<String> resourceNames = null;

        if (resourceParts.size() > 1) {
            // we need only parts that shows path to property
            // the last part is either lambda or name of the property we want to
            // filter by, so we ignore it
            resourceNames = resourceParts.subList(0, resourceParts.size() - 1).stream()
                    .filter(resource -> resource instanceof UriResourceComplexProperty
                            || resource instanceof UriResourcePrimitiveProperty)
                    .map(part -> ((UriResourceProperty) part).getProperty().getName())
                    .collect(Collectors.toList());
        }
        boolean namesListIsNotEmpty = resourceNames != null && !resourceNames.isEmpty();
        if (namesListIsNotEmpty && !parentPathPrefix.isEmpty()) {
            parentPathPrefix += NESTED_PATH_SEPARATOR;
        }

        return namesListIsNotEmpty
                ? parentPathPrefix + String.join(NESTED_PATH_SEPARATOR, resourceNames) : parentPath;
    }

    /**
     * Processes raw olingo expression member.
     * 
     * @param collectionResourceCache
     *            cache with parent members collection resources. Used for
     *            nested lambdas
     * @return expression member
     * @throws ODataApplicationException
     *             OData app exception
     * @throws ExpressionVisitException
     *             expression visitor exception
     */
    public ExpressionMember handle(Map<String, UriResource> collectionResourceCache)
            throws ODataApplicationException, ExpressionVisitException {
        this.collectionResourceCache = collectionResourceCache;
        if (lastPart instanceof UriResourceLambdaAll) {
            return throwNotImplemented("All lambda is not implemented");
        } else if (lastPart instanceof UriResourceLambdaAny) {
            return handleLambdaAny();
        } else if (lastPart instanceof UriResourcePrimitiveProperty
                || lastPart instanceof UriResourceLambdaVariable) {
            return handlePrimitive();
        } else {
            return throwNotImplemented();
        }
    }

    /**
     * Analyzes uri parts and creates a member. Lambda has expression that
     * should be executed to get the inner query.
     *
     * @return nested or child expression member
     */
    private ExpressionMember handleLambdaAny()
            throws ODataApplicationException, ExpressionVisitException {
        UriResourceLambdaAny lambda = (UriResourceLambdaAny) lastPart;
        Expression expression = lambda.getExpression();

        boolean isNavigationLambdaVar = firstPart instanceof UriResourcePartTyped
                && ((UriResourcePartTyped) firstPart).getType() instanceof EdmEntityType;
        if (firstPart instanceof UriResourceNavigation || isNavigationLambdaVar) {
            boolean isParentNestedLambdaVar = resourceParts.stream()
                    .anyMatch(part -> part instanceof UriResourceComplexProperty);
            List<String> navigationTypes = collectNavigationTypes();
            if (isParentNestedLambdaVar) {
                // navigation parent nested collection
                // book?$filter=author/_dimension/any(d:d/name eq 'Validity')
                ExpressionResult lambdaResult = handleLambdaAny(expression);
                return new ParentWrapperMember(navigationTypes, lambdaResult.getQueryBuilder())
                        .any();
            } else {
                if (resourceParts.size() > 2) {
                    // navigation parent to another child
                    // book?$filter=author/address/any(a:a/city eq 'New York'))
                    List<String> parentTypes = navigationTypes.subList(0,
                            navigationTypes.size() - 1);
                    return new ParentWrapperMember(parentTypes,
                            handleChildLambda(lambda).getQueryBuilder()).any();
                } else {
                    // navigation child property collection
                    // author?$filter=book/any(b:b/character/any(c:c/name eq
                    // 'Oliver'))
                    return handleChildLambda(lambda);
                }
            }
        } else {
            // complex or primitive type collection
            return handleLambdaAny(expression);
        }
    }

    private ExpressionResult handleLambdaAny(Expression lambdaExpression)
            throws ODataApplicationException, ExpressionVisitException {
        setPath(lambdaExpression);
        // if any lambda uses primitive property
        // Books?$filter=property/any(p:p eq 'value')
        // than parent path already contains path and property name
        // that's why we need to retrieve only nested path
        String nestedPath = isPreLastResourcePrimitive()
                ? StringUtils.substringBeforeLast(pathToMember, NESTED_PATH_SEPARATOR)
                : pathToMember;

        ExpressionResult expressionResult = (ExpressionResult) lambdaExpression.accept(visitor);
        return isPreLastResourcePrimitive() ? expressionResult
                : new NestedMember(nestedPath, expressionResult.getQueryBuilder()).any();
    }

    private ExpressionResult handleChildLambda(UriResourceLambdaAny lambda)
            throws ExpressionVisitException, ODataApplicationException {
        ExpressionResult lambdaResult = (ExpressionResult) lambda.getExpression().accept(visitor);
        // pre-last resource - before lambda; it's always a collection type
        UriResourceNavigation preLastNavResource = (UriResourceNavigation) resourceParts
                .get(resourceParts.size() - 2);
        ElasticEdmEntityType entityType = (ElasticEdmEntityType) preLastNavResource.getProperty()
                .getType();
        return new ChildMember(entityType.getESType(), lambdaResult.getQueryBuilder()).any();
    }

    private boolean isPreLastResourcePrimitive() {
        UriResource preLastResource = resourceParts.get(resourceParts.size() - 2);
        return preLastResource.getKind() == UriResourceKind.primitiveProperty;
    }

    private void setPath(Expression expression) {
        if (expression instanceof Member) {
            setPath((Member) expression);
        } else if (expression instanceof Binary) {
            Binary binaryExpression = (Binary) expression;
            setPath(binaryExpression.getLeftOperand());
            setPath(binaryExpression.getRightOperand());
        } else if (expression instanceof Method) {
            Method method = (Method) expression;
            method.getParameters().forEach(this::setPath);
        }
    }

    private void setPath(Member member) {
        UriInfoImpl memberUriInfo = (UriInfoImpl) member.getResourcePath();
        memberUriInfo.setFragment(pathToMember);
    }

    /**
     * Analyzes uri parts and creates primitive or parent expression member.
     * Also handles primitive expressions inside lambda's expression.
     * 
     * @return primitive or parent expression member
     */
    private ExpressionMember handlePrimitive() {
        // filter by parent's property
        // Books?$filter=Author/Name eq 'Dawkins'
        if (firstPart instanceof UriResourceNavigation) {
            EdmProperty lastProperty = ((UriResourceProperty) lastPart).getProperty();
            PrimitiveMember primitiveMember = new PrimitiveMember(
                    ((ElasticEdmProperty) lastProperty).getEField(), lastProperty.getAnnotations());
            return new ParentPrimitiveMember(collectNavigationTypes(), primitiveMember);