io.micronaut.data.document.processor.matchers.MongoRawQueryMethodMatcher Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of micronaut-data-document-processor Show documentation
Show all versions of micronaut-data-document-processor Show documentation
Data Repository Support for Micronaut
/*
* 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);
}
}