Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.micronaut.web.router.AbstractRouteMatch Maven / Gradle / Ivy
/*
* 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.web.router;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.bind.ArgumentBinder;
import io.micronaut.core.convert.ArgumentConversionContext;
import io.micronaut.core.convert.ConversionContext;
import io.micronaut.core.convert.ConversionError;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.convert.exceptions.ConversionErrorException;
import io.micronaut.core.type.Argument;
import io.micronaut.core.type.ReturnType;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.bind.RequestBinderRegistry;
import io.micronaut.http.bind.binders.PendingRequestBindingResult;
import io.micronaut.http.bind.binders.PostponedRequestArgumentBinder;
import io.micronaut.http.bind.binders.RequestArgumentBinder;
import io.micronaut.http.bind.binders.UnmatchedRequestArgumentBinder;
import io.micronaut.inject.ExecutableMethod;
import io.micronaut.inject.MethodExecutionHandle;
import io.micronaut.inject.UnsafeExecutionHandle;
import io.micronaut.web.router.exceptions.UnsatisfiedRouteException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* Abstract implementation of the {@link RouteMatch} interface.
*
* @param The target type
* @param Route Match
* @author Graeme Rocher
* @author Denis Stepanov
* @since 1.0
*/
abstract class AbstractRouteMatch implements MethodBasedRouteMatch {
protected final ConversionService conversionService;
protected final MethodBasedRouteInfo routeInfo;
protected final MethodExecutionHandle methodExecutionHandle;
protected final UnsafeExecutionHandle unsafeMethodExecutionHandle;
protected final ExecutableMethod executableMethod;
private final Argument>[] arguments;
private final String[] argumentNames;
private final Object[] argumentValues;
private final PostponedRequestArgumentBinder[] postponedArgumentBinders;
private final PendingRequestBindingResult>[] pendingRequestBindingResults;
private final boolean[] fulfilledArguments;
private boolean fulfilled;
private boolean beforeBindersApplied;
private boolean afterBindersApplied;
/**
* Constructor.
*
* @param routeInfo The route info
* @param conversionService The conversion service
*/
protected AbstractRouteMatch(MethodBasedRouteInfo routeInfo, ConversionService conversionService) {
this.routeInfo = routeInfo;
this.conversionService = conversionService;
this.methodExecutionHandle = routeInfo.getTargetMethod();
this.executableMethod = methodExecutionHandle.getExecutableMethod();
this.arguments = executableMethod.getArguments();
this.argumentNames = routeInfo.getArgumentNames();
int length = arguments.length;
if (length == 0) {
fulfilled = true;
this.argumentValues = null;
this.fulfilledArguments = null;
this.postponedArgumentBinders = null;
this.pendingRequestBindingResults = null;
} else {
this.argumentValues = new Object[length];
this.fulfilledArguments = new boolean[length];
this.postponedArgumentBinders = new PostponedRequestArgumentBinder[length];
this.pendingRequestBindingResults = new PendingRequestBindingResult[length];
}
if (methodExecutionHandle instanceof UnsafeExecutionHandle, ?>) {
unsafeMethodExecutionHandle = (UnsafeExecutionHandle) methodExecutionHandle;
} else {
unsafeMethodExecutionHandle = null;
}
}
@Override
public RouteInfo getRouteInfo() {
return routeInfo;
}
@Override
public T getTarget() {
return routeInfo.getTargetMethod().getTarget();
}
@NonNull
@Override
public ExecutableMethod getExecutableMethod() {
return executableMethod;
}
@Override
@NonNull
public AnnotationMetadata getAnnotationMetadata() {
return executableMethod.getAnnotationMetadata();
}
@Override
public Optional> getRequiredInput(String name) {
for (int i = 0; i < argumentNames.length; i++) {
String argumentName = argumentNames[i];
if (name.equals(argumentName)) {
return Optional.of(arguments[i]);
}
}
return Optional.empty();
}
@Override
public boolean isFulfilled() {
if (fulfilled) {
return true;
}
for (int i = 0; i < arguments.length; i++) {
boolean isFulfilled = fulfilledArguments[i];
if (isFulfilled) {
continue;
}
PendingRequestBindingResult> pendingRequestBindingResult = pendingRequestBindingResults[i];
if (pendingRequestBindingResult != null && !pendingRequestBindingResult.isPending()) {
Argument> argument = arguments[i];
setBindingResult(i, argument, pendingRequestBindingResult);
failOnConversionErrors(argument, pendingRequestBindingResult);
}
}
checkIfFulfilled();
return fulfilled;
}
@Override
public boolean isSatisfied(String name) {
for (int i = 0; i < argumentNames.length; i++) {
String argumentName = argumentNames[i];
if (name.equals(argumentName)) {
return fulfilledArguments[i];
}
}
return false;
}
@Override
public Method getTargetMethod() {
return routeInfo.getTargetMethod().getTargetMethod();
}
@Override
public String getMethodName() {
return executableMethod.getMethodName();
}
@Override
public Class getDeclaringType() {
return executableMethod.getDeclaringType();
}
@Override
public Argument>[] getArguments() {
return executableMethod.getArguments();
}
@Override
public ReturnType getReturnType() {
return executableMethod.getReturnType();
}
@Override
public R invoke(Object... arguments) {
Argument>[] targetArguments = getArguments();
if (targetArguments.length == 0) {
return methodExecutionHandle.invoke();
} else {
List argumentList = new ArrayList<>(arguments.length);
Map variables = getVariableValues();
Iterator valueIterator = variables.values().iterator();
int i = 0;
for (Argument> targetArgument : targetArguments) {
String name = targetArgument.getName();
Object value = variables.get(name);
if (value != null) {
Optional> result = conversionService.convert(value, targetArgument.getType());
argumentList.add(result.orElseThrow(() -> new IllegalArgumentException("Wrong argument types to method: " + executableMethod)));
} else if (valueIterator.hasNext()) {
Optional> result = conversionService.convert(valueIterator.next(), targetArgument.getType());
argumentList.add(result.orElseThrow(() -> new IllegalArgumentException("Wrong argument types to method: " + executableMethod)));
} else if (i < arguments.length) {
Optional> result = conversionService.convert(arguments[i++], targetArgument.getType());
argumentList.add(result.orElseThrow(() -> new IllegalArgumentException("Wrong argument types to method: " + executableMethod)));
} else {
throw new IllegalArgumentException("Wrong number of arguments to method: " + executableMethod);
}
}
return methodExecutionHandle.invoke(argumentList.toArray());
}
}
@Override
public R execute() {
Argument>[] targetArguments = getArguments();
if (targetArguments.length == 0) {
if (unsafeMethodExecutionHandle != null) {
return unsafeMethodExecutionHandle.invokeUnsafe();
}
return methodExecutionHandle.invoke();
}
if (fulfilled) {
if (unsafeMethodExecutionHandle != null) {
return unsafeMethodExecutionHandle.invokeUnsafe(argumentValues);
}
return methodExecutionHandle.invoke(argumentValues);
}
if (!beforeBindersApplied) {
throw new IllegalStateException("Argument binders before filters not processed!");
}
if (!afterBindersApplied) {
throw new IllegalStateException("Argument binders after filters not processed!");
}
for (int i = 0; i < arguments.length; i++) {
if (fulfilledArguments[i]) {
continue;
}
PendingRequestBindingResult> pendingRequestBindingResult = pendingRequestBindingResults[i];
Argument> argument = arguments[i];
if (pendingRequestBindingResult != null) {
setBindingResultOfFail(i, argument, pendingRequestBindingResult);
continue;
}
Object value = getVariableValues().get(argumentNames[i]);
if (value != null) {
setValue(i, argument, value);
continue;
}
if (argument.isOptional()) {
setValue(i, argument, Optional.empty());
continue;
}
if (!argument.isNullable()) {
throw UnsatisfiedRouteException.create(argument);
}
}
if (methodExecutionHandle instanceof UnsafeExecutionHandle) {
UnsafeExecutionHandle unsafeExecutionHandle = (UnsafeExecutionHandle) methodExecutionHandle;
return unsafeExecutionHandle.invokeUnsafe(argumentValues);
}
return methodExecutionHandle.invoke(argumentValues);
}
@Override
public void fulfill(Map newValues) {
if (fulfilled) {
return;
}
for (int i = 0; i < argumentNames.length; i++) {
if (fulfilledArguments[i]) {
continue;
}
String argumentName = argumentNames[i];
Object value = newValues.get(argumentName);
if (value != null) {
setValue(i, arguments[i], value);
}
}
checkIfFulfilled();
}
@Override
public void fulfillBeforeFilters(RequestBinderRegistry requestBinderRegistry, HttpRequest> request) {
if (fulfilled) {
return;
}
if (beforeBindersApplied) {
throw new IllegalStateException("Argument before filters already processed!");
}
RequestArgumentBinder[] argumentBinders = routeInfo.resolveArgumentBinders(requestBinderRegistry);
for (int i = 0; i < arguments.length; i++) {
if (fulfilledArguments[i]) {
continue;
}
Argument argument = (Argument) arguments[i];
Object value = getVariableValues().get(argumentNames[i]);
if (value != null) {
setValue(i, argument, value);
continue;
}
RequestArgumentBinder argumentBinder = argumentBinders[i];
if (argumentBinder instanceof PostponedRequestArgumentBinder postponedRequestArgumentBinder) {
postponedArgumentBinders[i] = postponedRequestArgumentBinder;
if (!(argumentBinder instanceof UnmatchedRequestArgumentBinder)) {
// Allow for the unmatched request argument binder to run even so it's postponed
continue;
}
}
if (argumentBinder != null) {
fulfillValue(
i,
argumentBinder,
argument,
request
);
}
}
checkIfFulfilled();
beforeBindersApplied = true;
}
@Override
public void fulfillAfterFilters(RequestBinderRegistry requestBinderRegistry, HttpRequest> request) {
if (fulfilled) {
return;
}
if (afterBindersApplied) {
throw new IllegalStateException("Argument binders after filters already processed!");
}
for (int i = 0; i < arguments.length; i++) {
if (fulfilledArguments[i]) {
continue;
}
Argument argument = (Argument) arguments[i];
PostponedRequestArgumentBinder argumentBinder = postponedArgumentBinders[i];
if (argumentBinder != null) {
fulfillValuePostponed(
i,
argumentBinder,
argument,
request
);
}
}
checkIfFulfilled();
afterBindersApplied = true;
}
private void fulfillValue(int index,
RequestArgumentBinder argumentBinder,
Argument argument,
HttpRequest> request) {
ArgumentConversionContext conversionContext = newContext(argument, request);
ArgumentBinder.BindingResult bindingResult = argumentBinder.bind(conversionContext, request);
fulfillValue(index, argument, bindingResult);
}
private void fulfillValuePostponed(int index,
PostponedRequestArgumentBinder argumentBinder,
Argument argument,
HttpRequest> request) {
ArgumentConversionContext conversionContext = newContext(argument, request);
ArgumentBinder.BindingResult bindingResult = argumentBinder.bindPostponed(conversionContext, request);
fulfillValue(index, argument, bindingResult);
}
private ArgumentConversionContext newContext(Argument argument, HttpRequest> request) {
ArgumentConversionContext conversionContext = ConversionContext.of(
argument,
request.getLocale().orElse(null),
request.getCharacterEncoding()
);
return conversionContext;
}
private void fulfillValue(int index, Argument argument, ArgumentBinder.BindingResult bindingResult) {
if (bindingResult instanceof PendingRequestBindingResult> pendingRequestBindingResult) {
pendingRequestBindingResults[index] = pendingRequestBindingResult;
return;
}
failOnConversionErrors(argument, bindingResult);
setBindingResult(index, argument, bindingResult);
}
private void setBindingResultOfFail(int index, Argument> argument, ArgumentBinder.BindingResult> bindingResult) {
boolean isSet = setBindingResult(index, argument, bindingResult);
failOnConversionErrors(argument, bindingResult);
if (isSet) {
return;
}
if (argument.isNullable()) {
setValue(index, argument, null);
return;
}
if (argument.isOptional()) {
setValue(index, argument, Optional.empty());
return;
}
throw UnsatisfiedRouteException.create(argument);
}
private void failOnConversionErrors(Argument> argument, ArgumentBinder.BindingResult> bindingResult) {
List conversionErrors = bindingResult.getConversionErrors();
if (!conversionErrors.isEmpty()) {
// should support multiple errors
ConversionError conversionError = conversionErrors.iterator().next();
throw new ConversionErrorException(argument, conversionError);
}
}
private boolean setBindingResult(int index, Argument> argument, ArgumentBinder.BindingResult> bindingResult) {
if (!bindingResult.isSatisfied()) {
return false;
}
Object value;
if (argument.getType() == Optional.class) {
Optional> optionalValue = bindingResult.getValue();
if (optionalValue.isPresent()) {
value = optionalValue.get();
} else {
return false;
}
} else if (bindingResult.isPresentAndSatisfied()) {
value = bindingResult.get();
} else {
return false;
}
setValue(index, argument, value);
return true;
}
private void setValue(int index, Argument> argument, Object value) {
if (value != null) {
argumentValues[index] = convertValue(conversionService, argument, value);
}
fulfilledArguments[index] = true;
}
private void checkIfFulfilled() {
if (fulfilled) {
return;
}
for (boolean isFulfilled : fulfilledArguments) {
if (!isFulfilled) {
return;
}
}
fulfilled = true;
}
private Object convertValue(ConversionService conversionService, Argument> argument, Object value) {
if (value instanceof ConversionError conversionError) {
throw new ConversionErrorException(argument, conversionError);
}
Class> argumentType = argument.getType();
if (argumentType.isInstance(value)) {
if (argument.isContainerType()) {
if (argument.hasTypeVariables()) {
ConversionContext conversionContext = ConversionContext.of(argument);
Optional> result = conversionService.convert(value, argumentType, conversionContext);
return resolveValueOrError(argument, conversionContext, result);
}
}
return value;
} else {
ConversionContext conversionContext = ConversionContext.of(argument);
Optional> result = conversionService.convert(value, argumentType, conversionContext);
return resolveValueOrError(argument, conversionContext, result);
}
}
private Object resolveValueOrError(Argument> argument, ConversionContext conversionContext, Optional> result) {
if (result.isEmpty()) {
Optional lastError = conversionContext.getLastError();
if (lastError.isEmpty() && argument.isDeclaredNullable()) {
return null;
}
throw lastError.map(conversionError ->
(RuntimeException) new ConversionErrorException(argument, conversionError)).orElseGet(() -> UnsatisfiedRouteException.create(argument)
);
}
return result.get();
}
}