use of io.micronaut.core.async.subscriber.CompletionAwareSubscriber in project micronaut-core by micronaut-projects.
the class RoutingInBoundHandler method buildSubscriber.
private Subscriber<Object> buildSubscriber(NettyHttpRequest<?> request, RouteMatch<?> finalRoute, MonoSink<RouteMatch<?>> emitter) {
boolean isFormData = request.isFormOrMultipartData();
if (isFormData) {
return new CompletionAwareSubscriber<Object>() {
final boolean alwaysAddContent = request.isFormData();
RouteMatch<?> routeMatch = finalRoute;
final AtomicBoolean executed = new AtomicBoolean(false);
final AtomicLong pressureRequested = new AtomicLong(0);
final ConcurrentHashMap<String, Sinks.Many<Object>> subjectsByDataName = new ConcurrentHashMap<>();
final Collection<Sinks.Many<Object>> downstreamSubscribers = Collections.synchronizedList(new ArrayList<>());
final ConcurrentHashMap<IdentityWrapper, HttpDataReference> dataReferences = new ConcurrentHashMap<>();
final ConversionService conversionService = ConversionService.SHARED;
Subscription s;
final LongConsumer onRequest = num -> pressureRequested.updateAndGet(p -> {
long newVal = p - num;
if (newVal < 0) {
s.request(num - p);
return 0;
} else {
return newVal;
}
});
Flux processFlowable(Sinks.Many<Object> many, HttpDataReference dataReference, boolean controlsFlow) {
Flux flux = many.asFlux();
if (controlsFlow) {
flux = flux.doOnRequest(onRequest);
}
return flux.doAfterTerminate(() -> {
if (controlsFlow) {
dataReference.destroy();
}
});
}
@Override
protected void doOnSubscribe(Subscription subscription) {
this.s = subscription;
subscription.request(1);
}
@Override
protected void doOnNext(Object message) {
boolean executed = this.executed.get();
if (message instanceof ByteBufHolder) {
if (message instanceof HttpData) {
HttpData data = (HttpData) message;
if (LOG.isTraceEnabled()) {
LOG.trace("Received HTTP Data for request [{}]: {}", request, message);
}
String name = data.getName();
Optional<Argument<?>> requiredInput = routeMatch.getRequiredInput(name);
if (requiredInput.isPresent()) {
Argument<?> argument = requiredInput.get();
Supplier<Object> value;
boolean isPublisher = Publishers.isConvertibleToPublisher(argument.getType());
boolean chunkedProcessing = false;
if (isPublisher) {
HttpDataReference dataReference = dataReferences.computeIfAbsent(new IdentityWrapper(data), key -> new HttpDataReference(data));
Argument typeVariable;
if (StreamingFileUpload.class.isAssignableFrom(argument.getType())) {
typeVariable = ARGUMENT_PART_DATA;
} else {
typeVariable = argument.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
}
Class typeVariableType = typeVariable.getType();
Sinks.Many<Object> namedSubject = subjectsByDataName.computeIfAbsent(name, key -> makeDownstreamUnicastProcessor());
chunkedProcessing = PartData.class.equals(typeVariableType) || Publishers.isConvertibleToPublisher(typeVariableType) || ClassUtils.isJavaLangType(typeVariableType);
if (Publishers.isConvertibleToPublisher(typeVariableType)) {
boolean streamingFileUpload = StreamingFileUpload.class.isAssignableFrom(typeVariableType);
if (streamingFileUpload) {
typeVariable = ARGUMENT_PART_DATA;
} else {
typeVariable = typeVariable.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
}
dataReference.subject.getAndUpdate(subject -> {
if (subject == null) {
Sinks.Many<Object> childSubject = makeDownstreamUnicastProcessor();
Flux flowable = processFlowable(childSubject, dataReference, true);
if (streamingFileUpload && data instanceof FileUpload) {
namedSubject.tryEmitNext(new NettyStreamingFileUpload((FileUpload) data, serverConfiguration.getMultipart(), getIoExecutor(), (Flux<PartData>) flowable));
} else {
namedSubject.tryEmitNext(flowable);
}
return childSubject;
}
return subject;
});
}
Sinks.Many<Object> subject;
final Sinks.Many<Object> ds = dataReference.subject.get();
if (ds != null) {
subject = ds;
} else {
subject = namedSubject;
}
Object part = data;
if (chunkedProcessing) {
HttpDataReference.Component component;
try {
component = dataReference.addComponent();
if (component == null) {
s.request(1);
return;
}
} catch (IOException e) {
subject.tryEmitError(e);
s.cancel();
return;
}
part = new NettyPartData(dataReference, component);
}
if (data instanceof FileUpload && StreamingFileUpload.class.isAssignableFrom(argument.getType())) {
dataReference.upload.getAndUpdate(upload -> {
if (upload == null) {
return new NettyStreamingFileUpload((FileUpload) data, serverConfiguration.getMultipart(), getIoExecutor(), (Flux<PartData>) processFlowable(subject, dataReference, true));
}
return upload;
});
}
Optional<?> converted = conversionService.convert(part, typeVariable);
converted.ifPresent(subject::tryEmitNext);
if (data.isCompleted() && chunkedProcessing) {
subject.tryEmitComplete();
}
value = () -> {
StreamingFileUpload upload = dataReference.upload.get();
if (upload != null) {
return upload;
} else {
return processFlowable(namedSubject, dataReference, dataReference.subject.get() == null);
}
};
} else {
if (data instanceof Attribute && !data.isCompleted()) {
request.addContent(data);
s.request(1);
return;
} else {
value = () -> {
if (data.refCnt() > 0) {
return data;
} else {
return null;
}
};
}
}
if (!executed) {
String argumentName = argument.getName();
if (!routeMatch.isSatisfied(argumentName)) {
Object fulfillParamter = value.get();
routeMatch = routeMatch.fulfill(Collections.singletonMap(argumentName, fulfillParamter));
// the data to the request ensures it is cleaned up after the route completes.
if (!alwaysAddContent && fulfillParamter instanceof ByteBufHolder) {
request.addContent((ByteBufHolder) fulfillParamter);
}
}
if (isPublisher && chunkedProcessing) {
// accounting for the previous request
pressureRequested.incrementAndGet();
}
if (routeMatch.isExecutable() || message instanceof LastHttpContent) {
executeRoute();
executed = true;
}
}
if (alwaysAddContent) {
request.addContent(data);
}
if (!executed || !chunkedProcessing) {
s.request(1);
}
} else {
request.addContent(data);
s.request(1);
}
} else {
request.addContent((ByteBufHolder) message);
s.request(1);
}
} else {
((NettyHttpRequest) request).setBody(message);
s.request(1);
}
}
@Override
protected void doOnError(Throwable t) {
s.cancel();
for (Sinks.Many<Object> subject : downstreamSubscribers) {
subject.tryEmitError(t);
}
emitter.error(t);
}
@Override
protected void doOnComplete() {
for (Sinks.Many<Object> subject : downstreamSubscribers) {
// subjects will ignore the onComplete if they're already done
subject.tryEmitComplete();
}
executeRoute();
}
private Sinks.Many<Object> makeDownstreamUnicastProcessor() {
Sinks.Many<Object> processor = Sinks.many().unicast().onBackpressureBuffer();
downstreamSubscribers.add(processor);
return processor;
}
private void executeRoute() {
if (executed.compareAndSet(false, true)) {
emitter.success(routeMatch);
}
}
};
} else {
return new CompletionAwareSubscriber<Object>() {
private Subscription s;
private RouteMatch<?> routeMatch = finalRoute;
private AtomicBoolean executed = new AtomicBoolean(false);
@Override
protected void doOnSubscribe(Subscription subscription) {
this.s = subscription;
subscription.request(1);
}
@Override
protected void doOnNext(Object message) {
if (message instanceof ByteBufHolder) {
request.addContent((ByteBufHolder) message);
s.request(1);
} else {
((NettyHttpRequest) request).setBody(message);
s.request(1);
}
}
@Override
protected void doOnError(Throwable t) {
s.cancel();
emitter.error(t);
}
@Override
protected void doOnComplete() {
if (executed.compareAndSet(false, true)) {
emitter.success(routeMatch);
}
}
};
}
}
use of io.micronaut.core.async.subscriber.CompletionAwareSubscriber in project micronaut-core by micronaut-projects.
the class JsonContentProcessor method doOnSubscribe.
@Override
protected void doOnSubscribe(Subscription subscription, Subscriber<? super JsonNode> subscriber) {
if (parentSubscription == null) {
return;
}
boolean streamArray = false;
boolean isJsonStream = nettyHttpRequest.getContentType().map(mediaType -> mediaType.equals(MediaType.APPLICATION_JSON_STREAM_TYPE)).orElse(false);
if (subscriber instanceof TypedSubscriber) {
TypedSubscriber typedSubscriber = (TypedSubscriber) subscriber;
Argument typeArgument = typedSubscriber.getTypeArgument();
Class targetType = typeArgument.getType();
if (Publishers.isConvertibleToPublisher(targetType) && !Publishers.isSingle(targetType)) {
Optional<Argument<?>> genericArgument = typeArgument.getFirstTypeVariable();
if (genericArgument.isPresent() && !Iterable.class.isAssignableFrom(genericArgument.get().getType()) && !isJsonStream) {
// if the generic argument is not a iterable type them stream the array into the publisher
streamArray = true;
}
}
}
this.jacksonProcessor = jsonMapper.createReactiveParser(p -> {
}, streamArray);
this.jacksonProcessor.subscribe(new CompletionAwareSubscriber<JsonNode>() {
@Override
protected void doOnSubscribe(Subscription jsonSubscription) {
Subscription childSubscription = new Subscription() {
boolean first = true;
@Override
public synchronized void request(long n) {
// find a better way in the future
if (first) {
jsonSubscription.request(n < Long.MAX_VALUE ? n + 1 : n);
parentSubscription.request(n < Long.MAX_VALUE ? n + 1 : n);
} else {
jsonSubscription.request(n);
parentSubscription.request(n);
}
}
@Override
public synchronized void cancel() {
jsonSubscription.cancel();
parentSubscription.cancel();
}
};
subscriber.onSubscribe(childSubscription);
}
@Override
protected void doOnNext(JsonNode message) {
subscriber.onNext(message);
}
@Override
protected void doOnError(Throwable t) {
subscriber.onError(t);
}
@Override
protected void doOnComplete() {
subscriber.onComplete();
}
});
jacksonProcessor.onSubscribe(subscription);
}
use of io.micronaut.core.async.subscriber.CompletionAwareSubscriber 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();
}
use of io.micronaut.core.async.subscriber.CompletionAwareSubscriber in project micronaut-core by micronaut-projects.
the class Publishers method mapOrSupplyEmpty.
/**
* Map the result from a publisher using the given mapper or supply empty value.
*
* @param publisher The publisher
* @param mapOrSupplyEmpty The mapOrSupplyEmpty
* @param <T> The generic type
* @param <R> The result type
* @since 2.5.0
* @return The mapped publisher
*/
public static <T, R> Publisher<R> mapOrSupplyEmpty(Publisher<T> publisher, MapOrSupplyEmpty<T, R> mapOrSupplyEmpty) {
return (MicronautPublisher<R>) actual -> publisher.subscribe(new CompletionAwareSubscriber<T>() {
AtomicBoolean resultPresent = new AtomicBoolean();
@Override
protected void doOnSubscribe(Subscription subscription) {
actual.onSubscribe(subscription);
}
@Override
protected void doOnNext(T message) {
try {
R result = Objects.requireNonNull(mapOrSupplyEmpty.map(message), "The mapper returned a null value.");
actual.onNext(result);
resultPresent.set(true);
} catch (Throwable e) {
onError(e);
}
}
@Override
protected void doOnError(Throwable t) {
actual.onError(t);
}
@Override
protected void doOnComplete() {
if (!resultPresent.get()) {
actual.onNext(mapOrSupplyEmpty.supplyEmpty());
}
actual.onComplete();
}
});
}
Aggregations