
io.muserver.rest.ResourceClass 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.TagObject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.NameBinding;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.ext.ParamConverterProvider;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Parameter;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toList;
class ResourceClass {
final UriPattern pathPattern;
private final Class> resourceClass;
final Object resourceInstance;
final List produces;
final List consumes;
List resourceMethods;
final String pathTemplate;
final TagObject tag;
final List> nameBindingAnnotations;
private final SchemaObjectCustomizer schemaObjectCustomizer;
/**
* If this class is sub-resource, then this is the locator method. Otherwise null.
*/
final ResourceMethod locatorMethod;
private ResourceClass(UriPattern pathPattern, String pathTemplate, Class> resourceClass, Object resourceInstance, List consumes, List produces, TagObject tag, List> nameBindingAnnotations, SchemaObjectCustomizer schemaObjectCustomizer, ResourceMethod locatorMethod) {
this.pathPattern = pathPattern;
this.pathTemplate = pathTemplate;
this.resourceClass = resourceClass;
this.resourceInstance = resourceInstance;
this.consumes = consumes;
this.produces = produces;
this.tag = tag;
this.nameBindingAnnotations = nameBindingAnnotations;
this.schemaObjectCustomizer = schemaObjectCustomizer;
this.locatorMethod = locatorMethod;
}
public boolean matches(URI uri) {
return pathPattern.matcher(uri).prefixMatches();
}
Set nonSubResourceMethods() {
return resourceMethods.stream().filter(resourceMethod -> !resourceMethod.isSubResource()).collect(Collectors.toSet());
}
Set subResourceMethods() {
return resourceMethods.stream().filter(ResourceMethod::isSubResource).collect(Collectors.toSet());
}
private void setupMethodInfo(List paramConverterProviders) {
if (resourceMethods != null) {
throw new IllegalStateException("Cannot call setupMethodInfo twice");
}
List resourceMethods = new ArrayList<>();
java.lang.reflect.Method[] methods = this.resourceClass.getMethods();
for (java.lang.reflect.Method restMethod : methods) {
java.lang.reflect.Method annotationSource = JaxMethodLocator.getMethodThatHasJaxRSAnnotations(restMethod);
Method httpMethod = ResourceMethod.getMuMethod(annotationSource);
restMethod.setAccessible(true);
Path methodPath = annotationSource.getAnnotation(Path.class);
if (methodPath == null && httpMethod == null) {
continue; // after this, only methods that are (sub)resource-methods or resource locators are processed
}
List> methodNameBindingAnnotations = getNameBindingAnnotations(annotationSource);
UriPattern methodPattern = methodPath == null ? null : UriPattern.uriTemplateToRegex(methodPath.value());
List methodProduces = MediaTypeDeterminer.supportedProducesTypes(annotationSource);
List methodConsumes = MediaTypeDeterminer.supportedConsumesTypes(annotationSource);
List params = new ArrayList<>();
Parameter[] parameters = annotationSource.getParameters();
for (int i = 0; i < parameters.length; i++) {
Parameter p = parameters[i];
ResourceMethodParam resourceMethodParam = ResourceMethodParam.fromParameter(i, p, paramConverterProviders, methodPattern);
params.add(resourceMethodParam);
}
DescriptionData descriptionData = DescriptionData.fromAnnotation(restMethod, null);
String pathTemplate = methodPath == null ? null : methodPath.value();
boolean isDeprecated = annotationSource.isAnnotationPresent(Deprecated.class);
resourceMethods.add(new ResourceMethod(this, methodPattern, restMethod, params, httpMethod, pathTemplate, methodProduces, methodConsumes, schemaObjectCustomizer, descriptionData, isDeprecated, methodNameBindingAnnotations, annotationSource.getAnnotations()));
}
this.resourceMethods = Collections.unmodifiableList(resourceMethods);
}
static List> getNameBindingAnnotations(AnnotatedElement annotationSource) {
return Stream.of(annotationSource.getAnnotations())
.filter(a -> a.annotationType().isAnnotationPresent(NameBinding.class))
.map(Annotation::annotationType)
.collect(toList());
}
static ResourceClass fromObject(Object restResource, List paramConverterProviders, SchemaObjectCustomizer schemaObjectCustomizer) {
Class> annotationSource = JaxClassLocator.getClassWithJaxRSAnnotations(restResource.getClass());
if (annotationSource == null) {
throw new IllegalArgumentException("The restResource class " + restResource.getClass().getName() + " must have a " + Path.class.getName() + " annotation to be eligible as a REST resource.");
}
// From section 3.6 of the spec:
// JAX-RS annotations MAY be used on the methods and method parameters of a super-class or an implemented interface.
// Such annotations are inherited by a corresponding sub-class or implementation class method provided that method
// and its parameters do not have any JAX-RS annotations of its own. Annotations on a super-class take precedence
// over those on an implemented interface. If a subclass or implementation method has any JAX-RS annotations then
// all of the annotations on the super class or interface method are ignored.
Path path = annotationSource.getDeclaredAnnotation(Path.class);
if (path == null) {
for (Annotation other : annotationSource.getDeclaredAnnotations()) {
if (other.annotationType().getName().equals("javax.ws.rs.Path")) {
throw new IllegalArgumentException("The class " + annotationSource.getName() + " contains an old version " +
"of the JAX-RS implementation. The package name for JAX-RS resources has changed from 'javax.ws.rs' to " +
"'jakarta.ws.rs' in the 3.0.0 release of jakarta.ws.rs-api. Please change all references in your project to this new namespace in order to " +
"use the version of JAX-RS that mu-server implements (this may be as simple as doing a global find and " +
"replace for 'javax.ws.rs' to 'jakarta.ws.rs').");
}
}
throw new IllegalArgumentException("The class " + annotationSource.getName() + " must specify a " + Path.class.getName()
+ " annotation because it has other JAX RS annotations declared. (Note that @Path cannot be inherited if there are other JAX RS annotations declared on this class.)");
}
UriPattern pathPattern = UriPattern.uriTemplateToRegex(path.value());
List producesList = getProduces(null, annotationSource);
List consumesList = getConsumes(null, annotationSource);
List> classLevelNameBindingAnnotations = getNameBindingAnnotations(annotationSource);
TagObject tag = DescriptionData.fromAnnotation(annotationSource, annotationSource.getSimpleName()).toTag();
ResourceClass resourceClass = new ResourceClass(pathPattern, path.value(), restResource.getClass(), restResource, consumesList, producesList, tag, classLevelNameBindingAnnotations, schemaObjectCustomizer, null);
resourceClass.setupMethodInfo(paramConverterProviders);
return resourceClass;
}
private static List getProduces(List existing, Class> annotationSource) {
Produces produces = annotationSource.getAnnotation(Produces.class);
List producesList = new ArrayList<>(MediaTypeHeaderDelegate.fromStrings(produces == null ? null : asList(produces.value())));
if (existing != null) {
producesList.addAll(existing);
}
return producesList;
}
private static List getConsumes(List existing, Class> annotationSource) {
Consumes consumes = annotationSource.getAnnotation(Consumes.class);
List consumesList = new ArrayList<>(MediaTypeHeaderDelegate.fromStrings(consumes == null ? null : asList(consumes.value())));
if (existing != null) {
consumesList.addAll(existing);
}
return consumesList;
}
static ResourceClass forSubResourceLocator(ResourceMethod rm, Class> instanceClass, Object instance, SchemaObjectCustomizer schemaObjectCustomizer, List paramConverterProviders) {
List existingConsumes = rm.effectiveConsumes.isEmpty() || (rm.directlyConsumes.isEmpty() && rm.effectiveConsumes.size() == 1 && rm.effectiveConsumes.get(0) == MediaType.WILDCARD_TYPE) ? null : rm.effectiveConsumes;
List consumes = getConsumes(existingConsumes, instanceClass);
List existingProduces = rm.effectiveProduces.isEmpty() || (rm.directlyProduces.isEmpty() && rm.effectiveProduces.size() == 1 && rm.effectiveProduces.get(0) == MediaType.WILDCARD_TYPE) ? null : rm.effectiveProduces;
List produces = getProduces(existingProduces, instanceClass);
ResourceClass resourceClass = new ResourceClass(rm.pathPattern, rm.pathTemplate, instanceClass, instance, consumes, produces, rm.resourceClass.tag, rm.resourceClass.nameBindingAnnotations, schemaObjectCustomizer, rm);
resourceClass.setupMethodInfo(paramConverterProviders);
return resourceClass;
}
@Override
public String toString() {
return "ResourceClass{" + resourceClassName() + '}';
}
String resourceClassName() {
return resourceClass.getName();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy