
io.muserver.rest.ResourceMethod Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mu-server Show documentation
Show all versions of mu-server Show documentation
A simple but powerful web server framework
The newest version!
package io.muserver.rest;
import io.muserver.Method;
import io.muserver.openapi.*;
import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.core.MediaType;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static io.muserver.Mutils.nullOrEmpty;
import static io.muserver.openapi.HeaderObjectBuilder.headerObject;
import static io.muserver.openapi.MediaTypeObjectBuilder.mediaTypeObject;
import static io.muserver.openapi.OperationObjectBuilder.operationObject;
import static io.muserver.openapi.RequestBodyObjectBuilder.requestBodyObject;
import static io.muserver.openapi.ResponseObjectBuilder.responseObject;
import static io.muserver.openapi.ResponsesObjectBuilder.responsesObject;
import static io.muserver.openapi.SchemaObjectBuilder.schemaObject;
import static io.muserver.openapi.SchemaObjectBuilder.schemaObjectFrom;
import static java.util.Collections.singletonMap;
import static java.util.stream.Collectors.toMap;
class ResourceMethod {
final ResourceClass resourceClass;
final UriPattern pathPattern;
final java.lang.reflect.Method methodHandle;
final Method httpMethod;
final String pathTemplate;
final List effectiveConsumes;
final List directlyConsumes;
final List directlyProduces;
final List effectiveProduces;
final List params;
private final SchemaObjectCustomizer schemaObjectCustomizer;
private final DescriptionData descriptionData;
private final boolean isDeprecated;
private final List> nameBindingAnnotations;
final Annotation[] methodAnnotations; // the annotations defined on the method to be passed to the message body writers
ResourceMethod(ResourceClass resourceClass, UriPattern pathPattern, java.lang.reflect.Method methodHandle, List params, Method httpMethod, String pathTemplate, List produces, List consumes, SchemaObjectCustomizer schemaObjectCustomizer, DescriptionData descriptionData, boolean isDeprecated, List> nameBindingAnnotations, Annotation[] methodAnnotations) {
this.resourceClass = resourceClass;
this.pathPattern = pathPattern;
this.methodHandle = methodHandle;
this.params = params;
this.httpMethod = httpMethod;
this.pathTemplate = pathTemplate;
this.directlyProduces = produces;
this.directlyConsumes = consumes;
this.schemaObjectCustomizer = schemaObjectCustomizer;
this.descriptionData = descriptionData;
this.isDeprecated = isDeprecated;
this.nameBindingAnnotations = nameBindingAnnotations;
this.methodAnnotations = methodAnnotations;
this.effectiveProduces = !produces.isEmpty() ? produces : (!resourceClass.produces.isEmpty() ? resourceClass.produces : RequestMatcher.WILDCARD_AS_LIST);
this.effectiveConsumes = !consumes.isEmpty() ? consumes : (!resourceClass.consumes.isEmpty() ? resourceClass.consumes : RequestMatcher.WILDCARD_AS_LIST);
}
boolean hasAll(List> annotations) {
for (Class extends Annotation> annotation : annotations) {
if (!nameBindingAnnotations.contains(annotation) && !resourceClass.nameBindingAnnotations.contains(annotation)) {
return false;
}
}
return true;
}
List paramsIncludingLocators() {
if (resourceClass.locatorMethod == null) {
return params;
}
List all = new ArrayList<>(resourceClass.locatorMethod.paramsIncludingLocators());
all.addAll(params);
return all;
}
boolean isSubResource() {
return pathPattern != null;
}
boolean isSubResourceLocator() {
return httpMethod == null;
}
Object invoke(Object... params) throws Exception {
try {
return methodHandle.invoke(resourceClass.resourceInstance, params);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof Exception) {
throw (Exception) cause;
}
throw e;
}
}
OperationObjectBuilder createOperationBuilder(List customSchemas) {
List apiResponseList = getApiResponses(methodHandle);
Map httpStatusCodes = new HashMap<>();
for (ApiResponseObj apiResponse : apiResponseList) {
Class> responseClass = apiResponse.response;
Stream responseTypesStream = apiResponse.contentType.length != 0 ?
Stream.of(apiResponse.contentType).map(MediaType::valueOf)
: "204".equals(apiResponse.code) ? null : effectiveProduces.stream();
Map content = responseTypesStream == null ? null : responseTypesStream.collect(toMap(MediaType::toString,
mt -> {
SchemaObject responseSchema;
Object example = nullOrEmpty(apiResponse.example) ? null : apiResponse.example;
if (void.class.isAssignableFrom(responseClass)) {
responseSchema = null;
} else {
SchemaReference schemaReference = SchemaReference.find(customSchemas, responseClass, apiResponse.genericReturnType);
SchemaObjectBuilder builder = schemaReference == null ? schemaObjectFrom(responseClass) : schemaReference.schema.toBuilder();
responseSchema = schemaObjectCustomizer.customize(builder,
schemaContext(SchemaObjectCustomizerTarget.RESPONSE_BODY, null, responseClass, apiResponse.genericReturnType, mt)).build();
if (responseSchema.example() != null) {
example = responseSchema.example();
}
}
return mediaTypeObject()
.withSchema(responseSchema)
.withExample(example)
.build();
}));
httpStatusCodes.put(apiResponse.code, responseObject()
.withContent(content)
.withDescription(apiResponse.message)
.withHeaders(
apiResponse.responseHeaders.length == 0 ? null :
Stream.of(apiResponse.responseHeaders).collect(
toMap(ResponseHeader::name,
rh -> headerObject().withDescription(rh.description())
.withDeprecated(rh.deprecated() ? true : null)
.withExample(nullOrEmpty(rh.example()) ? null : rh.example()).build()
)))
.build());
}
MediaType requestBodyMediaType = effectiveConsumes.get(0);
String requestBodyMimeType = requestBodyMediaType.toString();
RequestBodyObject requestBody = params.stream()
.filter(p -> p instanceof ResourceMethodParam.MessageBodyParam)
.map(ResourceMethodParam.MessageBodyParam.class::cast)
.map(messageBodyParam -> {
Class> bodyType = messageBodyParam.parameterHandle.getType();
Type bodyParameterizedType = messageBodyParam.parameterHandle.getParameterizedType();
SchemaReference schemaReference = SchemaReference.find(customSchemas, bodyType, bodyParameterizedType);
SchemaObjectBuilder builder = schemaReference != null ? schemaReference.schema.toBuilder() :
schemaObjectFrom(bodyType, bodyParameterizedType, messageBodyParam.isRequired)
.withTitle(messageBodyParam.descriptionData.summary)
.withDescription(messageBodyParam.descriptionData.description);
return requestBodyObject()
.withContent(singletonMap(requestBodyMimeType,
mediaTypeObject()
.withSchema(
schemaObjectCustomizer.customize(builder,
schemaContext(SchemaObjectCustomizerTarget.REQUEST_BODY, null, bodyType, bodyParameterizedType, requestBodyMediaType))
.build())
.withExample(messageBodyParam.descriptionData.example)
.build()))
.withDescription(messageBodyParam.descriptionData.summaryAndDescription())
.withRequired(messageBodyParam.isRequired)
.build();
})
.findFirst().orElse(null);
if (requestBody == null) {
List formParams = params.stream()
.filter(p -> p instanceof ResourceMethodParam.RequestBasedParam)
.map(ResourceMethodParam.RequestBasedParam.class::cast)
.filter(p -> p.source == ResourceMethodParam.ValueSource.FORM_PARAM)
.collect(Collectors.toList());
if (!formParams.isEmpty()) {
List required = new ArrayList<>();
requestBody = requestBodyObject()
.withContent(singletonMap(requestBodyMimeType,
mediaTypeObject()
.withSchema(
schemaObject()
.withType("object")
.withRequired(required)
.withProperties(
formParams.stream().collect(
toMap(n -> n.key,
n -> {
if (n.isRequired) {
required.add(n.key);
}
Class> paramType = n.parameterHandle.getType();
Type paramParameterizedType = n.parameterHandle.getParameterizedType();
SchemaReference schemaReference = SchemaReference.find(customSchemas, paramType, paramParameterizedType);
SchemaObjectBuilder schemaObjectBuilder = schemaReference != null ? schemaReference.schema.toBuilder() : schemaObjectFrom(paramType, paramParameterizedType, n.isRequired);
schemaObjectBuilder.withDeprecated(n.isDeprecated ? true : null);
if (n.hasExplicitDefault()) {
schemaObjectBuilder.withDefaultValue(n.defaultValue());
}
if (n.descriptionData != null) {
String desc = n.descriptionData.summaryAndDescription();
schemaObjectBuilder.withExample(n.descriptionData.example)
.withDescription(n.key.equals(desc) ? null : desc);
}
return schemaObjectCustomizer
.customize(schemaObjectBuilder, schemaContext(SchemaObjectCustomizerTarget.FORM_PARAM, n.key, paramType, paramParameterizedType, requestBodyMediaType))
.build();
}))
)
.build()
)
.build()
))
.build();
}
}
return operationObject()
.withSummary(descriptionData.summary)
.withDescription(descriptionData.description)
.withExternalDocs(descriptionData.externalDocumentation)
.withDeprecated(isDeprecated ? true : null)
.withRequestBody(requestBody)
.withResponses(
responsesObject()
.withHttpStatusCodes(httpStatusCodes)
.build())
;
}
private SchemaObjectCustomizerContext schemaContext(SchemaObjectCustomizerTarget target, String parameter, Class> type, Type parameterizedType, MediaType mediaType) {
return new SchemaObjectCustomizerContext(target, type, parameterizedType, resourceClass.resourceInstance, methodHandle, parameter, mediaType);
}
private static List getApiResponses(java.lang.reflect.Method methodHandle) {
ApiResponses apiResponses = methodHandle.getDeclaredAnnotation(ApiResponses.class);
List result = new ArrayList<>();
if (apiResponses != null) {
for (ApiResponse ar : apiResponses.value()) {
result.add(ApiResponseObj.fromAnnotation(ar, methodHandle));
}
}
ApiResponse single = methodHandle.getDeclaredAnnotation(ApiResponse.class);
if (single != null) {
result.add(ApiResponseObj.fromAnnotation(single, methodHandle));
}
if (result.isEmpty()) {
Class> returnType = methodHandle.getReturnType();
Type genericReturnType = methodHandle.getGenericReturnType();
String code = void.class.isAssignableFrom(returnType) ? "204" : "200";
result.add(new ApiResponseObj(code, "Success", new ResponseHeader[0], returnType, genericReturnType, new String[0], null));
}
return result;
}
static Method getMuMethod(java.lang.reflect.Method restMethod) {
Annotation[] annotations = restMethod.getAnnotations();
Method value = null;
for (Annotation annotation : annotations) {
Class extends Annotation> anno = annotation.annotationType();
HttpMethod httpMethodAnno = anno.getAnnotation(HttpMethod.class);
if (httpMethodAnno != null) {
if (value != null) {
throw new IllegalArgumentException("The method " + restMethod + " has multiple HttpMethod annotations. Only one is allowed per method.");
}
value = Method.valueOf(httpMethodAnno.value());
}
}
return value;
}
@Override
public String toString() {
return "ResourceMethod{" + resourceClass.resourceClassName() + "#" + methodHandle.getName() + "}";
}
boolean canProduceFor(List clientAccepts) {
return MediaTypeHeaderDelegate.atLeastOneCompatible(effectiveProduces, clientAccepts, null);
}
boolean canConsume(MediaType requestBodyMediaType) {
return MediaTypeHeaderDelegate.atLeastOneCompatible(effectiveConsumes, Collections.singletonList(requestBodyMediaType), null);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy