use of io.micronaut.core.convert.ArgumentConversionContext in project micronaut-core by micronaut-projects.
the class RequestArgumentSatisfier method getValueForArgument.
/**
* @param argument The argument
* @param request The HTTP request
* @param satisfyOptionals Whether to satisfy optionals
* @return An {@link Optional} for the value
*/
protected Optional<Object> getValueForArgument(Argument argument, HttpRequest<?> request, boolean satisfyOptionals) {
Object value = null;
Optional<ArgumentBinder> registeredBinder = binderRegistry.findArgumentBinder(argument, request);
if (registeredBinder.isPresent()) {
ArgumentBinder argumentBinder = registeredBinder.get();
ArgumentConversionContext conversionContext = ConversionContext.of(argument, request.getLocale().orElse(null), request.getCharacterEncoding());
if (argumentBinder instanceof BodyArgumentBinder) {
if (argumentBinder instanceof NonBlockingBodyArgumentBinder) {
ArgumentBinder.BindingResult bindingResult = argumentBinder.bind(conversionContext, request);
if (bindingResult.isPresentAndSatisfied()) {
value = bindingResult.get();
} else if (bindingResult.isSatisfied() && argument.isNullable()) {
value = NullArgument.INSTANCE;
}
} else {
value = getValueForBlockingBodyArgumentBinder(request, argumentBinder, conversionContext);
}
} else if (argumentBinder instanceof RequestBeanAnnotationBinder) {
// Resolve RequestBean after filters since some field types may depend on filters, i.e. Authentication
value = (UnresolvedArgument<?>) () -> argumentBinder.bind(conversionContext, request);
} else {
ArgumentBinder.BindingResult bindingResult = argumentBinder.bind(conversionContext, request);
if (argument.getType() == Optional.class) {
if (bindingResult.isSatisfied() || satisfyOptionals) {
Optional optionalValue = bindingResult.getValue();
if (optionalValue.isPresent()) {
value = optionalValue.get();
} else {
value = optionalValue;
}
}
} else if (bindingResult.isPresentAndSatisfied()) {
value = bindingResult.get();
} else if (bindingResult.isSatisfied() && argument.isNullable()) {
value = NullArgument.INSTANCE;
} else if (HttpMethod.requiresRequestBody(request.getMethod()) || argument.isNullable() || conversionContext.hasErrors()) {
value = (UnresolvedArgument) () -> {
ArgumentBinder.BindingResult result = argumentBinder.bind(conversionContext, request);
Optional<ConversionError> lastError = conversionContext.getLastError();
if (lastError.isPresent()) {
return (ArgumentBinder.BindingResult) () -> lastError;
}
return result;
};
}
}
}
return Optional.ofNullable(value);
}
use of io.micronaut.core.convert.ArgumentConversionContext in project micronaut-core by micronaut-projects.
the class MultipartBodyArgumentBinder method bind.
@Override
public BindingResult<MultipartBody> bind(ArgumentConversionContext<MultipartBody> context, HttpRequest<?> source) {
if (source instanceof NettyHttpRequest) {
NettyHttpRequest nettyHttpRequest = (NettyHttpRequest) source;
io.netty.handler.codec.http.HttpRequest nativeRequest = nettyHttpRequest.getNativeRequest();
if (nativeRequest instanceof StreamedHttpRequest) {
HttpContentProcessor<?> processor = beanLocator.findBean(HttpContentSubscriberFactory.class, new ConsumesMediaTypeQualifier<>(MediaType.MULTIPART_FORM_DATA_TYPE)).map(factory -> factory.build(nettyHttpRequest)).orElse(new DefaultHttpContentProcessor(nettyHttpRequest, httpServerConfiguration.get()));
// noinspection unchecked
return () -> Optional.of(subscriber -> processor.subscribe(new TypedSubscriber<Object>((Argument) context.getArgument()) {
Subscription s;
AtomicLong partsRequested = new AtomicLong(0);
@Override
protected void doOnSubscribe(Subscription subscription) {
this.s = subscription;
subscriber.onSubscribe(new Subscription() {
@Override
public void request(long n) {
if (partsRequested.getAndUpdate(prev -> prev + n) == 0) {
s.request(n);
}
}
@Override
public void cancel() {
subscription.cancel();
}
});
}
@Override
protected void doOnNext(Object message) {
if (LOG.isTraceEnabled()) {
LOG.trace("Server received streaming message for argument [{}]: {}", context.getArgument(), message);
}
if (message instanceof ByteBufHolder && ((ByteBufHolder) message).content() instanceof EmptyByteBuf) {
return;
}
if (message instanceof HttpData) {
HttpData data = (HttpData) message;
if (data.isCompleted()) {
partsRequested.decrementAndGet();
if (data instanceof FileUpload) {
subscriber.onNext(new NettyCompletedFileUpload((FileUpload) data, false));
} else if (data instanceof Attribute) {
subscriber.onNext(new NettyCompletedAttribute((Attribute) data, false));
}
// If the user didn't release the data, we should
if (data.refCnt() > 0) {
data.release();
}
}
}
if (partsRequested.get() > 0) {
s.request(1);
}
}
@Override
protected void doOnError(Throwable t) {
if (LOG.isTraceEnabled()) {
LOG.trace("Server received error for argument [" + context.getArgument() + "]: " + t.getMessage(), t);
}
try {
subscriber.onError(t);
} finally {
s.cancel();
}
}
@Override
protected void doOnComplete() {
if (LOG.isTraceEnabled()) {
LOG.trace("Done receiving messages for argument: {}", context.getArgument());
}
subscriber.onComplete();
}
}));
}
}
return BindingResult.EMPTY;
}
use of io.micronaut.core.convert.ArgumentConversionContext in project micronaut-core by micronaut-projects.
the class AbstractBeanDefinition method getValueForField.
/**
* Obtains a value for the given field from the bean context
* <p>
* Warning: this method is used by internal generated code and should not be called by user code.
*
* @param resolutionContext The resolution context
* @param context The context
* @param fieldIndex The index of the field
* @return The resolved bean
*/
@SuppressWarnings("WeakerAccess")
@Internal
@UsedByGeneratedCode
protected final Object getValueForField(BeanResolutionContext resolutionContext, BeanContext context, int fieldIndex) {
FieldInjectionPoint injectionPoint = fieldInjectionPoints.get(fieldIndex);
BeanResolutionContext.Path path = resolutionContext.getPath();
path.pushFieldResolve(this, injectionPoint);
try {
if (context instanceof PropertyResolver) {
final AnnotationMetadata annotationMetadata = injectionPoint.getAnnotationMetadata();
String valueAnnVal = annotationMetadata.stringValue(Value.class).orElse(null);
Argument<?> fieldArgument = injectionPoint.asArgument();
Argument<?> argumentType;
boolean isCollection = false;
if (Collection.class.isAssignableFrom(injectionPoint.getType())) {
argumentType = fieldArgument.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
isCollection = true;
} else {
argumentType = fieldArgument;
}
if (isInnerConfiguration(argumentType, context)) {
Qualifier qualifier = resolveQualifier(resolutionContext, fieldArgument, true);
if (isCollection) {
Collection beans = ((DefaultBeanContext) context).getBeansOfType(resolutionContext, argumentType, qualifier);
return coerceCollectionToCorrectType(fieldArgument.getType(), beans);
} else {
return ((DefaultBeanContext) context).getBean(resolutionContext, argumentType, qualifier);
}
} else {
String valString = resolvePropertyValueName(resolutionContext, injectionPoint, valueAnnVal, annotationMetadata);
ArgumentConversionContext conversionContext = ConversionContext.of(fieldArgument);
Optional value = resolveValue((ApplicationContext) context, conversionContext, valueAnnVal != null, valString);
if (argumentType.isOptional()) {
return resolveOptionalObject(value);
} else {
if (value.isPresent()) {
return value.get();
} else {
if (fieldArgument.isDeclaredNullable()) {
return null;
}
throw new DependencyInjectionException(resolutionContext, injectionPoint, "Error resolving field value [" + valString + "]. Property doesn't exist or cannot be converted");
}
}
}
} else {
throw new DependencyInjectionException(resolutionContext, injectionPoint, "@Value requires a BeanContext that implements PropertyResolver");
}
} finally {
path.pop();
}
}
use of io.micronaut.core.convert.ArgumentConversionContext in project micronaut-core by micronaut-projects.
the class AbstractRouteMatch method fulfill.
@Override
public RouteMatch<R> fulfill(Map<String, Object> argumentValues) {
if (CollectionUtils.isEmpty(argumentValues)) {
return this;
} else {
Map<String, Object> oldVariables = getVariableValues();
Map<String, Object> newVariables = new LinkedHashMap<>(oldVariables);
final Argument<?> bodyArgument = getBodyArgument().orElse(null);
Argument[] arguments = getArguments();
Collection<Argument> requiredArguments = getRequiredArguments();
boolean hasRequiredArguments = CollectionUtils.isNotEmpty(requiredArguments);
for (Argument requiredArgument : arguments) {
String argumentName = requiredArgument.getName();
if (argumentValues.containsKey(argumentName)) {
Object value = argumentValues.get(argumentName);
if (bodyArgument != null && bodyArgument.getName().equals(argumentName)) {
requiredArgument = bodyArgument;
}
if (hasRequiredArguments) {
requiredArguments.remove(requiredArgument);
}
if (value != null) {
String name = abstractRoute.resolveInputName(requiredArgument);
if (value instanceof UnresolvedArgument || value instanceof NullArgument) {
newVariables.put(name, value);
} else {
Class type = requiredArgument.getType();
if (type.isInstance(value)) {
newVariables.put(name, value);
} else {
ArgumentConversionContext conversionContext = ConversionContext.of(requiredArgument);
Optional converted = conversionService.convert(value, conversionContext);
Object result = converted.isPresent() ? converted.get() : conversionContext.getLastError().orElse(null);
if (result != null) {
newVariables.put(name, result);
}
}
}
}
}
}
return newFulfilled(newVariables, (List<Argument>) requiredArguments);
}
}
use of io.micronaut.core.convert.ArgumentConversionContext in project micronaut-core by micronaut-projects.
the class HttpClientIntroductionAdvice method intercept.
/**
* Interceptor to apply headers, cookies, parameter and body arguements.
*
* @param context The context
* @return httpClient or future
*/
@Nullable
@Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
if (!context.hasStereotype(Client.class)) {
throw new IllegalStateException("Client advice called from type that is not annotated with @Client: " + context);
}
final AnnotationMetadata annotationMetadata = context.getAnnotationMetadata();
Class<?> declaringType = context.getDeclaringType();
if (Closeable.class == declaringType || AutoCloseable.class == declaringType) {
clientFactory.disposeClient(annotationMetadata);
return null;
}
Optional<Class<? extends Annotation>> httpMethodMapping = context.getAnnotationTypeByStereotype(HttpMethodMapping.class);
HttpClient httpClient = clientFactory.getClient(annotationMetadata);
if (context.hasStereotype(HttpMethodMapping.class) && httpClient != null) {
AnnotationValue<HttpMethodMapping> mapping = context.getAnnotation(HttpMethodMapping.class);
String uri = mapping.getRequiredValue(String.class);
if (StringUtils.isEmpty(uri)) {
uri = "/" + context.getMethodName();
}
Class<? extends Annotation> annotationType = httpMethodMapping.get();
HttpMethod httpMethod = HttpMethod.parse(annotationType.getSimpleName().toUpperCase(Locale.ENGLISH));
String httpMethodName = context.stringValue(CustomHttpMethod.class, "method").orElse(httpMethod.name());
MutableHttpRequest<?> request = HttpRequest.create(httpMethod, "", httpMethodName);
UriMatchTemplate uriTemplate = UriMatchTemplate.of("");
if (!(uri.length() == 1 && uri.charAt(0) == '/')) {
uriTemplate = uriTemplate.nest(uri);
}
Map<String, Object> pathParams = new HashMap<>();
Map<String, List<String>> queryParams = new LinkedHashMap<>();
ClientRequestUriContext uriContext = new ClientRequestUriContext(uriTemplate, pathParams, queryParams);
List<Argument> bodyArguments = new ArrayList<>();
List<String> uriVariables = uriTemplate.getVariableNames();
Map<String, MutableArgumentValue<?>> parameters = context.getParameters();
ClientArgumentRequestBinder<Object> defaultBinder = (ctx, uriCtx, value, req) -> {
Argument<?> argument = ctx.getArgument();
if (uriCtx.getUriTemplate().getVariableNames().contains(argument.getName())) {
String name = argument.getAnnotationMetadata().stringValue(Bindable.class).orElse(argument.getName());
// Convert and put as path param
if (argument.getAnnotationMetadata().hasStereotype(Format.class)) {
ConversionService.SHARED.convert(value, ConversionContext.STRING.with(argument.getAnnotationMetadata())).ifPresent(v -> pathParams.put(name, v));
} else {
pathParams.put(name, value);
}
} else {
bodyArguments.add(ctx.getArgument());
}
};
// Apply all the method binders
List<Class<? extends Annotation>> methodBinderTypes = context.getAnnotationTypesByStereotype(Bindable.class);
// @Version is not a bindable, so it needs to looked for separately
methodBinderTypes.addAll(context.getAnnotationTypesByStereotype(Version.class));
if (!CollectionUtils.isEmpty(methodBinderTypes)) {
for (Class<? extends Annotation> binderType : methodBinderTypes) {
binderRegistry.findAnnotatedBinder(binderType).ifPresent(b -> b.bind(context, uriContext, request));
}
}
InterceptedMethod interceptedMethod = InterceptedMethod.of(context);
// Apply all the argument binders
Argument[] arguments = context.getArguments();
if (arguments.length > 0) {
for (Argument argument : arguments) {
Object definedValue = getValue(argument, context, parameters);
if (definedValue != null) {
final ClientArgumentRequestBinder<Object> binder = (ClientArgumentRequestBinder<Object>) binderRegistry.findArgumentBinder((Argument<Object>) argument).orElse(defaultBinder);
ArgumentConversionContext conversionContext = ConversionContext.of(argument);
binder.bind(conversionContext, uriContext, definedValue, request);
if (conversionContext.hasErrors()) {
return interceptedMethod.handleException(new ConversionErrorException(argument, conversionContext.getLastError().get()));
}
}
}
}
Object body = request.getBody().orElse(null);
if (body == null && !bodyArguments.isEmpty()) {
Map<String, Object> bodyMap = new LinkedHashMap<>();
for (Argument bodyArgument : bodyArguments) {
String argumentName = bodyArgument.getName();
MutableArgumentValue<?> value = parameters.get(argumentName);
bodyMap.put(argumentName, value.getValue());
}
body = bodyMap;
request.body(body);
}
boolean variableSatisfied = uriVariables.isEmpty() || pathParams.keySet().containsAll(uriVariables);
if (body != null && !variableSatisfied) {
if (body instanceof Map) {
for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) body).entrySet()) {
String k = entry.getKey().toString();
Object v = entry.getValue();
if (v != null) {
pathParams.putIfAbsent(k, v);
}
}
} else {
BeanMap<Object> beanMap = BeanMap.of(body);
for (Map.Entry<String, Object> entry : beanMap.entrySet()) {
String k = entry.getKey();
Object v = entry.getValue();
if (v != null) {
pathParams.putIfAbsent(k, v);
}
}
}
}
if (!HttpMethod.permitsRequestBody(httpMethod)) {
// If a binder set the body and the method does not permit it, reset to null
request.body(null);
body = null;
}
uri = uriTemplate.expand(pathParams);
// Remove all the pathParams that have already been used.
// Other path parameters are added to query
uriVariables.forEach(pathParams::remove);
addParametersToQuery(pathParams, uriContext);
// The original query can be added by getting it from the request.getUri() and appending
request.uri(URI.create(appendQuery(uri, uriContext.getQueryParameters())));
if (body != null && !request.getContentType().isPresent()) {
MediaType[] contentTypes = MediaType.of(context.stringValues(Produces.class));
if (ArrayUtils.isEmpty(contentTypes)) {
contentTypes = DEFAULT_ACCEPT_TYPES;
}
if (ArrayUtils.isNotEmpty(contentTypes)) {
request.contentType(contentTypes[0]);
}
}
request.setAttribute(HttpAttributes.INVOCATION_CONTEXT, context);
// Set the URI template used to make the request for tracing purposes
request.setAttribute(HttpAttributes.URI_TEMPLATE, resolveTemplate(annotationMetadata, uriTemplate.toString()));
String serviceId = getClientId(annotationMetadata);
Argument<?> errorType = annotationMetadata.classValue(Client.class, "errorType").map((Function<Class, Argument>) Argument::of).orElse(HttpClient.DEFAULT_ERROR_TYPE);
request.setAttribute(HttpAttributes.SERVICE_ID, serviceId);
final MediaType[] acceptTypes;
Collection<MediaType> accept = request.accept();
if (accept.isEmpty()) {
String[] consumesMediaType = context.stringValues(Consumes.class);
if (ArrayUtils.isEmpty(consumesMediaType)) {
acceptTypes = DEFAULT_ACCEPT_TYPES;
} else {
acceptTypes = MediaType.of(consumesMediaType);
}
request.accept(acceptTypes);
} else {
acceptTypes = accept.toArray(MediaType.EMPTY_ARRAY);
}
ReturnType<?> returnType = context.getReturnType();
try {
Argument<?> valueType = interceptedMethod.returnTypeValue();
Class<?> reactiveValueType = valueType.getType();
switch(interceptedMethod.resultType()) {
case PUBLISHER:
boolean isSingle = returnType.isSingleResult() || returnType.isCompletable() || HttpResponse.class.isAssignableFrom(reactiveValueType) || HttpStatus.class == reactiveValueType;
Publisher<?> publisher;
if (!isSingle && httpClient instanceof StreamingHttpClient) {
publisher = httpClientResponseStreamingPublisher((StreamingHttpClient) httpClient, acceptTypes, request, errorType, valueType);
} else {
publisher = httpClientResponsePublisher(httpClient, request, returnType, errorType, valueType);
}
Object finalPublisher = interceptedMethod.handleResult(publisher);
for (ReactiveClientResultTransformer transformer : transformers) {
finalPublisher = transformer.transform(finalPublisher);
}
return finalPublisher;
case COMPLETION_STAGE:
Publisher<?> csPublisher = httpClientResponsePublisher(httpClient, request, returnType, errorType, valueType);
CompletableFuture<Object> future = new CompletableFuture<>();
csPublisher.subscribe(new CompletionAwareSubscriber<Object>() {
AtomicReference<Object> reference = new AtomicReference<>();
@Override
protected void doOnSubscribe(Subscription subscription) {
subscription.request(1);
}
@Override
protected void doOnNext(Object message) {
if (Void.class != reactiveValueType) {
reference.set(message);
}
}
@Override
protected void doOnError(Throwable t) {
if (t instanceof HttpClientResponseException) {
HttpClientResponseException e = (HttpClientResponseException) t;
if (e.getStatus() == HttpStatus.NOT_FOUND) {
if (reactiveValueType == Optional.class) {
future.complete(Optional.empty());
} else if (HttpResponse.class.isAssignableFrom(reactiveValueType)) {
future.complete(e.getResponse());
} else {
future.complete(null);
}
return;
}
}
if (LOG.isErrorEnabled()) {
LOG.error("Client [" + declaringType.getName() + "] received HTTP error response: " + t.getMessage(), t);
}
future.completeExceptionally(t);
}
@Override
protected void doOnComplete() {
future.complete(reference.get());
}
});
return interceptedMethod.handleResult(future);
case SYNCHRONOUS:
Class<?> javaReturnType = returnType.getType();
BlockingHttpClient blockingHttpClient = httpClient.toBlocking();
if (void.class == javaReturnType || httpMethod == HttpMethod.HEAD) {
request.getHeaders().remove(HttpHeaders.ACCEPT);
}
if (HttpResponse.class.isAssignableFrom(javaReturnType)) {
return handleBlockingCall(javaReturnType, () -> blockingHttpClient.exchange(request, returnType.asArgument().getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT), errorType));
} else if (void.class == javaReturnType) {
return handleBlockingCall(javaReturnType, () -> blockingHttpClient.exchange(request, null, errorType));
} else {
return handleBlockingCall(javaReturnType, () -> blockingHttpClient.retrieve(request, returnType.asArgument(), errorType));
}
default:
return interceptedMethod.unsupported();
}
} catch (Exception e) {
return interceptedMethod.handleException(e);
}
}
// try other introduction advice
return context.proceed();
}
Aggregations