use of io.micronaut.http.uri.UriMatchTemplate in project micronaut-core by micronaut-projects.
the class ClientTypesRule method validate.
@Override
public RouteValidationResult validate(List<UriMatchTemplate> templates, ParameterElement[] parameters, MethodElement method) {
String[] errors = new String[] {};
if (method.hasAnnotation(Client.class)) {
final Stream.Builder<ClassElement> builder = Stream.<ClassElement>builder().add(method.getReturnType());
for (ParameterElement param : method.getParameters()) {
builder.add(param.getType());
}
errors = builder.build().filter(type -> {
for (Class<?> clazz : SERVER_TYPES) {
if (type.isAssignable(clazz)) {
return true;
}
}
return false;
}).map(type -> "The type [" + type + "] must not be used in declarative client methods. The type is specific to server based usages.").toArray(String[]::new);
}
return new RouteValidationResult(errors);
}
use of io.micronaut.http.uri.UriMatchTemplate in project micronaut-core by micronaut-projects.
the class DefaultRouter method findAllClosest.
@NonNull
@Override
public <T, R> List<UriRouteMatch<T, R>> findAllClosest(@NonNull HttpRequest<?> request) {
final HttpMethod httpMethod = request.getMethod();
final MediaType contentType = request.getContentType().orElse(null);
boolean permitsBody = HttpMethod.permitsRequestBody(httpMethod);
final Collection<MediaType> acceptedProducedTypes = request.accept();
List<UriRouteMatch<T, R>> uriRoutes = this.find(request.getMethodName(), request.getPath(), routeMatch -> routeMatch.test(request) && (!permitsBody || routeMatch.doesConsume(contentType)) && routeMatch.doesProduce(acceptedProducedTypes));
int routeCount = uriRoutes.size();
if (routeCount <= 1) {
return uriRoutes;
}
if (CollectionUtils.isNotEmpty(acceptedProducedTypes)) {
// take the highest priority accepted type
final MediaType mediaType = acceptedProducedTypes.iterator().next();
List<UriRouteMatch<T, R>> mostSpecific = new ArrayList<>(uriRoutes.size());
for (UriRouteMatch<T, R> routeMatch : uriRoutes) {
if (routeMatch.explicitlyProduces(mediaType)) {
mostSpecific.add(routeMatch);
}
}
if (!mostSpecific.isEmpty() || !acceptedProducedTypes.contains(MediaType.ALL_TYPE)) {
uriRoutes = mostSpecific;
}
}
routeCount = uriRoutes.size();
if (routeCount > 1 && permitsBody) {
List<UriRouteMatch<T, R>> explicitlyConsumedRoutes = new ArrayList<>(routeCount);
List<UriRouteMatch<T, R>> consumesRoutes = new ArrayList<>(routeCount);
for (UriRouteMatch<T, R> match : uriRoutes) {
if (match.explicitlyConsumes(contentType != null ? contentType : MediaType.ALL_TYPE)) {
explicitlyConsumedRoutes.add(match);
}
if (explicitlyConsumedRoutes.isEmpty() && match.doesConsume(contentType)) {
consumesRoutes.add(match);
}
}
uriRoutes = explicitlyConsumedRoutes.isEmpty() ? consumesRoutes : explicitlyConsumedRoutes;
}
/*
* Any changes to the logic below may also need changes to {@link io.micronaut.http.uri.UriTemplate#compareTo(UriTemplate)}
*/
routeCount = uriRoutes.size();
if (routeCount > 1) {
long variableCount = 0;
long rawLength = 0;
List<UriRouteMatch<T, R>> closestMatches = new ArrayList<>(routeCount);
for (int i = 0; i < routeCount; i++) {
UriRouteMatch<T, R> match = uriRoutes.get(i);
UriMatchTemplate template = match.getRoute().getUriMatchTemplate();
long variable = template.getPathVariableSegmentCount();
long raw = template.getRawSegmentLength();
if (i == 0) {
variableCount = variable;
rawLength = raw;
}
if (variable > variableCount || raw < rawLength) {
break;
}
closestMatches.add(match);
}
uriRoutes = closestMatches;
}
return uriRoutes;
}
use of io.micronaut.http.uri.UriMatchTemplate in project micronaut-core by micronaut-projects.
the class UriTemplateTest method testUriTemplate.
@Test
public void testUriTemplate() {
// tag::match[]
UriMatchTemplate template = UriMatchTemplate.of("/hello/{name}");
// <1>
assertTrue(template.match("/hello/John").isPresent());
assertEquals("/hello/John", // <2>
template.expand(Collections.singletonMap("name", "John")));
// end::match[]
}
use of io.micronaut.http.uri.UriMatchTemplate 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.http.uri.UriMatchTemplate in project micronaut-core by micronaut-projects.
the class NullableParameterRule method validate.
@Override
public RouteValidationResult validate(List<UriMatchTemplate> templates, ParameterElement[] parameters, MethodElement method) {
List<String> errorMessages = new ArrayList<>();
boolean isClient = method.hasAnnotation("io.micronaut.http.client.annotation.Client");
// Optional variables can be required in clients
if (!isClient) {
Map<String, UriMatchVariable> variables = new HashMap<>();
Set<UriMatchVariable> required = new HashSet<>();
for (UriMatchTemplate template : templates) {
for (UriMatchVariable variable : template.getVariables()) {
if (!variable.isOptional() || variable.isExploded()) {
required.add(variable);
}
variables.compute(variable.getName(), (key, var) -> {
if (var == null) {
if (variable.isOptional() && !variable.isExploded()) {
return variable;
} else {
return null;
}
} else {
if (!var.isOptional() || var.isExploded()) {
if (variable.isOptional() && !variable.isExploded()) {
return variable;
} else {
return var;
}
} else {
return var;
}
}
});
}
}
for (UriMatchVariable variable : required) {
if (templates.stream().anyMatch(t -> !t.getVariableNames().contains(variable.getName()))) {
variables.putIfAbsent(variable.getName(), variable);
}
}
for (UriMatchVariable variable : variables.values()) {
Arrays.stream(parameters).flatMap(p -> getTypedElements(p).stream()).filter(p -> p.getName().equals(variable.getName())).forEach(p -> {
ClassElement type = p.getType();
boolean hasDefaultValue = p.findAnnotation(Bindable.class).flatMap(av -> av.stringValue("defaultValue")).isPresent();
if (!isNullable(p) && type != null && !type.isAssignable(Optional.class) && !hasDefaultValue) {
errorMessages.add(String.format("The uri variable [%s] is optional, but the corresponding method argument [%s %s] is not defined as an Optional or annotated with a Nullable annotation.", variable.getName(), p.getType().toString(), p.getName()));
}
});
}
}
return new RouteValidationResult(errorMessages.toArray(new String[0]));
}
Aggregations