Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/project.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: SmallRye OpenAPI
release:
current-version: 3.6.0
next-version: 3.6.1-SNAPSHOT
next-version: 3.6.1-VB-SNAPSHOT
2 changes: 1 addition & 1 deletion core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-open-api-parent</artifactId>
<version>3.6.1-SNAPSHOT</version>
<version>3.6.1-VB-SNAPSHOT</version>
</parent>

<artifactId>smallrye-open-api-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,17 @@ public static SchemaImpl copyOf(Schema other) {
.map(SchemaImpl::copyOf)
.collect(Collectors.toList()));

clone.properties = copy(clone.properties, () -> clone.properties.entrySet()
Supplier<Map<String, Schema>> sup = () -> clone.properties.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> copyOf(e.getValue()),
(u, v) -> {
throw new IllegalStateException(String.format("Duplicate key %s", u));
},
LinkedHashMap::new)));
LinkedHashMap::new));

clone.properties = copy(clone.properties, sup);

clone.additionalPropertiesSchema = copy(clone.additionalPropertiesSchema,
() -> copyOf(clone.additionalPropertiesSchema));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,7 @@ protected void setSchemaProperties(Schema schema,
}

addEncoding(encodings, paramName, paramTarget);
setDefaultValue(paramSchema, getDefaultValue(paramTarget));
paramSchema = augmentFormParamSchema(paramSchema, paramType, getDefaultValue(paramTarget));
TypeUtil.mapDeprecated(paramTarget, paramSchema::getDeprecated, paramSchema::setDeprecated);

if (beanValidationScanner.isPresent()) {
Expand All @@ -708,6 +708,44 @@ protected void setSchemaProperties(Schema schema,
}
}

/**
* Set default value for @FormParam if any. Updates param's schema either directly or,
* when a <code>ref</code> is present, by creating a union of the local and reference schemas
* using <code>allOf</code>.
*
* @param paramSchema the Parameter containing a schema for update
* @param paramType Type associated with param
*/
Schema augmentFormParamSchema(Schema paramSchema, Type paramType, Object defaultValue) {
String ref = paramSchema.getRef();
Schema localSchema;

if (ref != null) {
/*
* Lookup the schema `type` from components (if available) or guess the type if
* the `ref` is not available.
*/
Schema refSchema = ModelUtil.getComponent(scannerContext.getOpenApi(), ref);

if (refSchema != null) {
localSchema = new SchemaImpl().type(refSchema.getType());
} else {
localSchema = new SchemaImpl().type(SchemaType
.valueOf(TypeUtil.getTypeAttributes(paramType).get(SchemaConstant.PROP_TYPE).toString().toUpperCase()));
}
} else {
localSchema = paramSchema;
}

int modCount = SchemaImpl.getModCount(localSchema);
setDefaultValue(localSchema, defaultValue);
if (localOnlySchemaModified(paramSchema, localSchema, modCount)) {
// Add new `allOf` schema, erasing `type` derived above from the local schema
return new SchemaImpl().addAllOf(paramSchema).addAllOf(localSchema.type(null));
}
return paramSchema;
}

/**
* Called by the BeanValidationScanner when a member is found to have a BV annotation
* indicating the parameter is required.
Expand Down
8 changes: 7 additions & 1 deletion extension-jaxrs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-open-api-parent</artifactId>
<version>3.6.1-SNAPSHOT</version>
<version>3.6.1-VB-SNAPSHOT</version>
</parent>

<artifactId>smallrye-open-api-jaxrs</artifactId>
Expand Down Expand Up @@ -57,6 +57,12 @@
<artifactId>resteasy-multipart-provider</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>${version.jersey.media}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus.resteasy.reactive</groupId>
<artifactId>resteasy-reactive-common</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -104,7 +106,8 @@ public boolean isMultipartOutput(Type returnType) {

@Override
public boolean isMultipartInput(Type inputType) {
return RestEasyConstants.MULTIPART_INPUTS.contains(inputType.name());
DotName name = inputType.name();
return JerseyConstants.MULTIPART_INPUTS.contains(name) || RestEasyConstants.MULTIPART_INPUTS.contains(name);
}

@Override
Expand Down Expand Up @@ -304,30 +307,70 @@ private Map<DotName, List<AnnotationInstance>> processExceptionMappers(final Ann
Collection<ClassInfo> exceptionMappers = new ArrayList<>();

for (DotName dn : JaxRsConstants.EXCEPTION_MAPPER) {
exceptionMappers.addAll(context.getIndex()
.getKnownDirectImplementors(dn));
Collection<ClassInfo> hierarchyChain = obtainHierarchyChain(dn, context);
exceptionMappers.addAll(hierarchyChain);
}
Map<Type, List<ClassInfo>> exceptionAndMappers = groupByExceptionType(exceptionMappers);

return exceptionMappers.stream()
.flatMap(this::exceptionResponseAnnotations)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
Map<DotName, List<AnnotationInstance>> exceptionWithAnnotations = new LinkedHashMap<>();
Comparator<ClassInfo> priorityComparator = JaxRsFactory.createPriorityComparator();
for (Type exceptionType : exceptionAndMappers.keySet()) {
List<ClassInfo> eMappers = exceptionAndMappers.get(exceptionType);

eMappers.sort(priorityComparator);
List<AnnotationInstance> annotations = exceptionWithAnnotations.computeIfAbsent(exceptionType.name(), (k) -> new ArrayList<>());
annotations.addAll(exceptionResponseAnnotations(eMappers.get(0), exceptionType));
}

return exceptionWithAnnotations;
}

private Stream<Entry<DotName, List<AnnotationInstance>>> exceptionResponseAnnotations(ClassInfo classInfo) {
private Collection<ClassInfo> obtainHierarchyChain(DotName root, AnnotationScannerContext context) {
FilteredIndexView index = context.getIndex();
Map<DotName, ClassInfo> result = new LinkedHashMap<>();

Type exceptionType = classInfo.interfaceTypes()
.stream()
.filter(it -> JaxRsConstants.EXCEPTION_MAPPER.contains(it.name()))
.filter(it -> Type.Kind.PARAMETERIZED_TYPE.equals(it.kind()))
.map(Type::asParameterizedType)
.map(type -> type.arguments().get(0))
.findAny()
.orElse(null);
Map<DotName, ClassInfo> knownDirectImplementors = index.getKnownDirectImplementors(root)
.stream().collect(Collectors.toMap(ClassInfo::name, Function.identity()));
result.putAll(knownDirectImplementors);
Map<DotName, ClassInfo> knownDirectSubclasses = index.getKnownDirectSubclasses(root)
.stream().collect(Collectors.toMap(ClassInfo::name, Function.identity()));
result.putAll(knownDirectSubclasses);

for (DotName implementorName : knownDirectImplementors.keySet()) {
Collection<ClassInfo> classInfos = obtainHierarchyChain(implementorName, context);
classInfos.forEach(ci -> result.put(ci.name(), ci));
}

for (DotName subclassName : knownDirectSubclasses.keySet()) {
Collection<ClassInfo> classInfos = obtainHierarchyChain(subclassName, context);
classInfos.forEach(ci -> result.put(ci.name(), ci));
}

if (exceptionType == null) {
return Stream.empty();
return result.values();
}

private Map<Type, List<ClassInfo>> groupByExceptionType(Collection<ClassInfo> exceptionMappers) {
LinkedHashMap<Type, List<ClassInfo>> result = new LinkedHashMap<>();
for (ClassInfo exceptionMapper : exceptionMappers) {
Type exceptionType = exceptionMapper.methods().stream()
.filter(m -> m.name().equals(JaxRsConstants.TO_RESPONSE_METHOD_NAME))
// Remove bridge methods, 0x00001000 is ACC_SYNTHETIC mask
.filter(m -> (m.flags() & 0x00001000) == 0)
// extract exception type, it is the only argument in contract
.map(m -> m.parameterType(0))
.findAny().orElse(null);

if (exceptionType == null) {
continue;
}

List<ClassInfo> classInfos = result.computeIfAbsent(exceptionType, (k) -> new ArrayList<>());
classInfos.add(exceptionMapper);
}
return result;
}

private List<AnnotationInstance> exceptionResponseAnnotations(ClassInfo classInfo, Type exceptionType) {
Stream<AnnotationInstance> methodAnnotations = Stream
.of(classInfo.method(JaxRsConstants.TO_RESPONSE_METHOD_NAME, exceptionType))
.filter(Objects::nonNull)
Expand All @@ -336,16 +379,9 @@ private Stream<Entry<DotName, List<AnnotationInstance>>> exceptionResponseAnnota
Stream<AnnotationInstance> classAnnotations = ResponseReader.getResponseAnnotations(classInfo).stream();

// Later annotations will eventually override earlier ones, so put class before method
List<AnnotationInstance> annotations = Stream
.concat(classAnnotations, methodAnnotations)
return Stream.concat(classAnnotations, methodAnnotations)
.filter(ResponseReader::hasResponseCodeValue)
.collect(Collectors.toList());

if (annotations.isEmpty()) {
return Stream.empty();
} else {
return Stream.of(entryOf(exceptionType.name(), annotations));
}
}

// Replace with Map.entry when available (Java 9+)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ public class JaxRsConstants {
static final Set<DotName> EXCEPTION_MAPPER = new TreeSet<>(Arrays.asList(
DotName.createSimple("javax.ws.rs.ext.ExceptionMapper"),
DotName.createSimple("jakarta.ws.rs.ext.ExceptionMapper")));
static final Set<DotName> PRIORITY = new TreeSet<>(Arrays.asList(
DotName.createSimple("javax.annotation.Priority"),
DotName.createSimple("jakarta.annotation.Priority")));
static final Set<DotName> QUERY_PARAM = new TreeSet<>(Arrays.asList(
DotName.createSimple("javax.ws.rs.QueryParam"),
DotName.createSimple("jakarta.ws.rs.QueryParam")));
Expand Down Expand Up @@ -104,6 +107,7 @@ public class JaxRsConstants {
.collect(Collectors.toSet());

static final String TO_RESPONSE_METHOD_NAME = "toResponse";
static final Integer PRIORITY_DEFAULT_VALUE = 5000;

private static final Set<DotName> methods = new LinkedHashSet<>();
static {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.smallrye.openapi.jaxrs;

import java.util.Comparator;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;

public class JaxRsFactory {

public static Comparator<ClassInfo> createPriorityComparator() {
return (first, second) -> {
Integer firstPriorityValue = JaxRsConstants.PRIORITY_DEFAULT_VALUE;
for (DotName dotName : JaxRsConstants.PRIORITY) {
AnnotationInstance annotation = first.annotation(dotName);
if (annotation == null) {
continue;
}

firstPriorityValue = (Integer) annotation.value("value").value();
}

Integer secondPriorityValue = JaxRsConstants.PRIORITY_DEFAULT_VALUE;
for (DotName dotName : JaxRsConstants.PRIORITY) {
AnnotationInstance annotation = second.annotation(dotName);
if (annotation == null) {
continue;
}
secondPriorityValue = (Integer) annotation.value("value").value();
}

return firstPriorityValue.compareTo(secondPriorityValue);
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ public enum JaxRsParameter {
COOKIE_PARAM(JaxRsConstants.COOKIE_PARAM, Parameter.In.COOKIE, null, Parameter.Style.FORM),
BEAN_PARAM(JaxRsConstants.BEAN_PARAM, null, null, null),

// Support Jersey annotations directly
JERSEY_MULTIPART_FORM(JerseyConstants.FORM_DATA_PARAM, null, Parameter.Style.FORM, Parameter.Style.FORM),

// Support RESTEasy annotations directly
RESTEASY_PATH_PARAM(RestEasyConstants.PATH_PARAM, Parameter.In.PATH, null, Parameter.Style.SIMPLE),
// Apply to the last-matched @Path of the structure injecting the MatrixParam
Expand Down Expand Up @@ -80,4 +83,8 @@ public static boolean isParameter(DotName annotationName) {
return forName(annotationName) != null;
}

public static boolean isJerseyParameter(DotName annotationName) {
return annotationName.toString().startsWith(JerseyConstants.JERSEY_PACKAGE);
}

}
Loading