All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.micronaut.data.document.processor.matchers.MongoRawQueryMethodMatcher Maven / Gradle / Ivy

There is a newer version: 4.9.3
Show newest version
/*
 * Copyright 2017-2020 original authors
 *
 * Licensed 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
 *
 * https://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 io.micronaut.data.document.processor.matchers;

import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.data.annotation.MappedEntity;
import io.micronaut.data.annotation.ParameterExpression;
import io.micronaut.data.annotation.Query;
import io.micronaut.data.annotation.TypeRole;
import io.micronaut.data.document.mongo.MongoAnnotations;
import io.micronaut.data.intercept.annotation.DataMethod;
import io.micronaut.data.model.query.BindingParameter;
import io.micronaut.data.model.query.builder.QueryParameterBinding;
import io.micronaut.data.model.query.builder.QueryResult;
import io.micronaut.data.processor.model.SourcePersistentEntity;
import io.micronaut.data.processor.visitors.MatchFailedException;
import io.micronaut.data.processor.visitors.MethodMatchContext;
import io.micronaut.data.processor.visitors.finders.FindersUtils;
import io.micronaut.data.processor.visitors.finders.MethodMatchInfo;
import io.micronaut.data.processor.visitors.finders.MethodMatcher;
import io.micronaut.data.processor.visitors.finders.RawQueryMethodMatcher;
import io.micronaut.data.processor.visitors.finders.TypeUtils;
import io.micronaut.inject.annotation.AnnotationMetadataHierarchy;
import io.micronaut.inject.annotation.MutableAnnotationMetadata;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.MethodElement;
import io.micronaut.inject.ast.ParameterElement;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * Finder with custom defied query used to return a single result.
 *
 * @author Denis Stepanov
 * @since 3.3.0
 */
public class MongoRawQueryMethodMatcher implements MethodMatcher {

    private static final Pattern VARIABLE_PATTERN = Pattern.compile("([^:]*)((? 1) {
                    entityParameter = null;
                    entitiesParameter = null;
                } else {
                    entityParameter = Arrays.stream(parameters).filter(p -> TypeUtils.isEntity(p.getGenericType())).findFirst().orElse(null);
                    entitiesParameter = Arrays.stream(parameters).filter(p -> TypeUtils.isIterableOfEntity(p.getGenericType())).findFirst().orElse(null);
                }

                FindersUtils.InterceptorMatch entry = FindersUtils.resolveInterceptorTypeByOperationType(
                        entityParameter != null,
                        entitiesParameter != null,
                        operationType,
                        matchContext);

                ClassElement resultType = entry.returnType();
                ClassElement interceptorType = entry.interceptor();

                boolean isDto = false;
                if (resultType == null) {
                    resultType = matchContext.getRootEntity().getType();
                } else {
                    if (resultType.hasAnnotation(Introspected.class)) {
                        if (!resultType.hasAnnotation(MappedEntity.class)) {
                            isDto = true;
                        }
                    }
                }

                MethodMatchInfo methodMatchInfo = new MethodMatchInfo(
                        operationType,
                        resultType,
                        interceptorType
                );

                methodMatchInfo.dto(isDto);

                buildRawQuery(matchContext, methodMatchInfo, entityParameter, entitiesParameter, operationType);

                if (entityParameter != null) {
                    methodMatchInfo.addParameterRole(TypeRole.ENTITY, entityParameter.getName());
                } else if (entitiesParameter != null) {
                    methodMatchInfo.addParameterRole(TypeRole.ENTITIES, entitiesParameter.getName());
                }
                return methodMatchInfo;
            }
        };
    }

    private void buildRawQuery(@NonNull MethodMatchContext matchContext,
                               MethodMatchInfo methodMatchInfo,
                               ParameterElement entityParameter,
                               ParameterElement entitiesParameter,
                               DataMethod.OperationType operationType) {
        MethodElement methodElement = matchContext.getMethodElement();
        List parameters = Arrays.asList(matchContext.getParameters());
        ParameterElement entityParam = null;
        SourcePersistentEntity persistentEntity = null;
        if (entityParameter != null) {
            entityParam = entityParameter;
            persistentEntity = matchContext.getEntity(entityParameter.getGenericType());
        } else if (entitiesParameter != null) {
            entityParam = entitiesParameter;
            persistentEntity = matchContext.getEntity(entitiesParameter.getGenericType().getFirstTypeArgument()
                    .orElseThrow(IllegalStateException::new));
        }

        QueryResult queryResult;
        if (operationType == DataMethod.OperationType.UPDATE) {
            queryResult = getUpdateQueryResult(matchContext, parameters, entityParam, persistentEntity);
        } else {
            queryResult = getQueryResult(matchContext, parameters, entityParam, persistentEntity);
        }
        boolean encodeEntityParameters = persistentEntity != null || operationType == DataMethod.OperationType.INSERT;

        methodElement.annotate(Query.class, builder -> {
            if (queryResult.getUpdate() != null) {
                builder.member("update", queryResult.getUpdate());
            }
            builder.value(queryResult.getQuery());
        });

        methodMatchInfo
                .encodeEntityParameters(encodeEntityParameters)
                .queryResult(queryResult)
                .countQueryResult(null);
    }

    private QueryResult getQueryResult(MethodMatchContext matchContext,
                                       List parameters,
                                       ParameterElement entityParam,
                                       SourcePersistentEntity persistentEntity) {
        String filterQueryString;
        if (matchContext.getMethodElement().hasAnnotation(MongoAnnotations.AGGREGATION_QUERY)) {
            filterQueryString = matchContext.getMethodElement().stringValue(MongoAnnotations.AGGREGATION_QUERY).orElseThrow(() ->
                    new MatchFailedException("The pipeline value is missing!")
            );
            removeAnnotation(matchContext.getAnnotationMetadata(), MongoAnnotations.AGGREGATION_QUERY); // Mapped to query
        } else if (matchContext.getMethodElement().hasAnnotation(MongoAnnotations.FIND_QUERY)) {
            filterQueryString = matchContext.getMethodElement().stringValue(MongoAnnotations.FILTER).orElseThrow(() ->
                    new MatchFailedException("The filter value is missing!")
            );
            removeAnnotation(matchContext.getAnnotationMetadata(), MongoAnnotations.FILTER); // Mapped to query
            removeAnnotation(matchContext.getAnnotationMetadata(), MongoAnnotations.FIND_QUERY); // Mapped to query
        } else if (matchContext.getMethodElement().hasAnnotation(MongoAnnotations.DELETE_QUERY)) {
            filterQueryString = matchContext.getMethodElement().stringValue(MongoAnnotations.FILTER).orElseThrow(() ->
                    new MatchFailedException("The filter value is missing!")
            );
            removeAnnotation(matchContext.getAnnotationMetadata(), MongoAnnotations.FILTER); // Mapped to query
            removeAnnotation(matchContext.getAnnotationMetadata(), MongoAnnotations.DELETE_QUERY); // Mapped to query
        } else {
            throw new MatchFailedException("Unknown custom query annotation!");
        }
        List parameterBindings = new ArrayList<>(parameters.size());
        String filterQuery = processCustomQuery(matchContext, filterQueryString, parameters, entityParam, persistentEntity, parameterBindings);
        return new QueryResult() {
            @Override
            public String getQuery() {
                return filterQuery;
            }

            @Override
            public List getQueryParts() {
                return Collections.emptyList();
            }

            @Override
            public List getParameterBindings() {
                return parameterBindings;
            }

            @Override
            public Map getAdditionalRequiredParameters() {
                return Collections.emptyMap();
            }
        };
    }

    private QueryResult getUpdateQueryResult(MethodMatchContext matchContext,
                                       List parameters,
                                       ParameterElement entityParam,
                                       SourcePersistentEntity persistentEntity) {
        String filterQueryString = matchContext.getMethodElement().stringValue(MongoAnnotations.FILTER).orElse("{}");
        String updateQueryString = matchContext.getMethodElement().stringValue(MongoAnnotations.UPDATE_QUERY, "update").orElseThrow(() ->
                new MatchFailedException("Update query is missing!")
        );
        removeAnnotation(matchContext.getAnnotationMetadata(), MongoAnnotations.FILTER); // Mapped to query
        removeAnnotation(matchContext.getAnnotationMetadata(), MongoAnnotations.UPDATE_QUERY); // Mapped to query
        List parameterBindings = new ArrayList<>(parameters.size());
        String filterQuery = processCustomQuery(matchContext, filterQueryString, parameters, entityParam, persistentEntity, parameterBindings);
        String updateQuery = processCustomQuery(matchContext, updateQueryString, parameters, entityParam, persistentEntity, parameterBindings);
        return new QueryResult() {
            @Override
            public String getQuery() {
                return filterQuery;
            }

            @Override
            public String getUpdate() {
                return updateQuery;
            }

            @Override
            public List getQueryParts() {
                return Collections.emptyList();
            }

            @Override
            public List getParameterBindings() {
                return parameterBindings;
            }

            @Override
            public Map getAdditionalRequiredParameters() {
                return Collections.emptyMap();
            }
        };
    }

    private String processCustomQuery(MethodMatchContext matchContext, String queryString, List parameters, ParameterElement entityParam, SourcePersistentEntity persistentEntity, List parameterBindings) {
        List> parameterExpressions = matchContext.getMethodElement()
            .getAnnotationMetadata()
            .getAnnotationValuesByType(ParameterExpression.class);

        java.util.regex.Matcher matcher = VARIABLE_PATTERN.matcher(queryString);
        List queryParts = new ArrayList<>();
        int lastOffset = 0;
        while (matcher.find()) {
            int matcherStart = matcher.start(3);
            String start = queryString.substring(lastOffset, matcherStart - 1);
            lastOffset = matcher.end(3);
            if (!start.isEmpty()) {
                queryParts.add(start);
            }

            String name = matcher.group(3);

            QueryParameterBinding binding = RawQueryMethodMatcher.addBinding(
                matchContext,
                parameters,
                parameterExpressions,
                entityParam,
                persistentEntity,
                name,
                BindingParameter.BindingContext.create().name(name)
            );

            parameterBindings.add(binding);

            int ind = parameterBindings.size() - 1;
            queryParts.add("{$mn_qp:" + ind + "}");
        }
        String end = queryString.substring(lastOffset);
        if (!end.isEmpty()) {
            queryParts.add(end);
        }
        return String.join("", queryParts);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy