Skip to content
Merged
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
44 changes: 35 additions & 9 deletions core/src/main/java/io/apicurio/hub/api/codegen/OpenApi2JaxRs.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ public class OpenApi2JaxRs {

static final Map<String, Type<?>> TYPE_CACHE = new HashMap<>();
static final String OPENAPI_OPERATION_ANNOTATION = "org.eclipse.microprofile.openapi.annotations.Operation";
private static final String MEDIA_TYPE_CONSTANT_PREFIX = "MediaType.";

protected static ObjectMapper mapper = new ObjectMapper();
protected static Charset utf8 = StandardCharsets.UTF_8;
Expand Down Expand Up @@ -603,9 +604,14 @@ protected String generateJavaInterface(CodegenInfo info, CodegenJavaInterface in

Optional.ofNullable(methodInfo.getConsumes())
.filter(Predicate.not(Collection::isEmpty))
.map(OpenApi2JaxRs::toStringArrayLiteral)
.ifPresent(consumes ->
operationMethod.addAnnotation(String.format("%s.ws.rs.Consumes", topLevelPackage)).setLiteralValue(consumes));
.ifPresent(consumesSet -> {
// Add MediaType import if any consumes value uses MediaType constants
if (consumesSet.stream().anyMatch(consume -> consume.contains(MEDIA_TYPE_CONSTANT_PREFIX))) {
resourceInterface.addImport(String.format("%s.ws.rs.core.MediaType", topLevelPackage));
}
String consumesLiteral = toStringArrayLiteral(consumesSet);
operationMethod.addAnnotation(String.format("%s.ws.rs.Consumes", topLevelPackage)).setLiteralValue(consumesLiteral);
});

final boolean reactive;

Expand Down Expand Up @@ -638,6 +644,10 @@ protected String generateJavaInterface(CodegenInfo info, CodegenJavaInterface in
if (arg.getIn().equals("body")) {
// Swagger 2.0?
defaultParamType = InputStream.class.getName();
} else if (arg.getIn().equals("form")
&& arg.getType() != null
&& !arg.getType().isEmpty()) {
defaultParamType = arg.getType().get(0);
}

Type<?> paramType = generateTypeName(arg, arg.getRequired(), defaultParamType);
Expand Down Expand Up @@ -667,6 +677,11 @@ protected String generateJavaInterface(CodegenInfo info, CodegenJavaInterface in
param.addAnnotation(String.format("%s.ws.rs.CookieParam", topLevelPackage))
.setStringValue(arg.getName());
break;
case "form":
param.addAnnotation("org.jboss.resteasy.annotations.providers.multipart.RestForm")
.setStringValue(arg.getName());
resourceInterface.addImport("org.jboss.resteasy.annotations.providers.multipart.RestForm");
break;
default:
break;
}
Expand Down Expand Up @@ -876,19 +891,30 @@ protected static String toStringArrayLiteral(Set<String> values) {
StringBuilder builder = new StringBuilder();

if (values.size() == 1) {
builder.append("\"");
builder.append(values.iterator().next().replace("\"", "\\\""));
builder.append("\"");
String value = values.iterator().next();
if (value.startsWith(MEDIA_TYPE_CONSTANT_PREFIX)) {
// Don't quote MediaType constants
builder.append(value);
} else {
builder.append("\"");
builder.append(value.replace("\"", "\\\""));
builder.append("\"");
}
} else {
builder.append("{");
boolean first = true;
for (String value : values) {
if (!first) {
builder.append(", ");
}
builder.append("\"");
builder.append(value.replace("\"", "\\\""));
builder.append("\"");
if (value.startsWith(MEDIA_TYPE_CONSTANT_PREFIX)) {
// Don't quote MediaType constants
builder.append(value);
} else {
builder.append("\"");
builder.append(value.replace("\"", "\\\""));
builder.append("\"");
}
first = false;
}
builder.append("}");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
package io.apicurio.hub.api.codegen.jaxrs;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import io.apicurio.datamodels.models.Document;
import io.apicurio.datamodels.models.Schema;
import io.apicurio.datamodels.models.openapi.OpenApiMediaType;
import io.apicurio.datamodels.models.openapi.v31.OpenApi31Schema;
import io.apicurio.hub.api.codegen.JaxRsProjectSettings;
import io.apicurio.hub.api.codegen.beans.CodegenJavaArgument;
import io.apicurio.hub.api.codegen.beans.CodegenJavaMethod;
import io.apicurio.hub.api.codegen.util.CodegenUtil;

import static io.apicurio.hub.api.codegen.util.CodegenUtil.containsValue;

/**
* Utility class for processing multipart/form-data request bodies.
* Extracts form-field parameters from OpenAPI schema properties.
*
* @author <a href="https://github.com/lpieprzyk">lpieprzyk</a>
*/
public class MultipartFormDataRequestBodyProcessor {

private static final String JAVA_LANG_STRING = "java.lang.String";
private static final String JBOSS_FILE_UPLOAD = "org.jboss.resteasy.plugins.providers.multipart.FileUpload";
private static final String JAVA_TIME_LOCAL_DATE = "java.time.LocalDate";
private static final String JAVA_TIME_LOCAL_DATE_TIME = "java.time.LocalDateTime";
private static final String JAVA_LANG_LONG = "java.lang.Long";
private static final String JAVA_LANG_INTEGER = "java.lang.Integer";
private static final String JAVA_LANG_DOUBLE = "java.lang.Double";
private static final String JAVA_LANG_FLOAT = "java.lang.Float";
private static final String JAVA_MATH_BIG_DECIMAL = "java.math.BigDecimal";
private static final String JAVA_UTIL_LIST = "java.util.List";
private static final String JAVA_UTIL_MAP = "java.util.Map";
private static final String JAVA_LANG_OBJECT = "java.lang.Object";
private static final String JAVA_LANG_BOOLEAN = "java.lang.Boolean";

private MultipartFormDataRequestBodyProcessor() {
// Functional class
}

/**
* Processes a multipart/form-data media type by creating individual form field parameters
* instead of a generic body parameter.
*
* @param mediaType the OpenAPI media type to process
* @param methodTemplate the method template to modify with form field parameters
* @param settings the project settings for type mapping
* @param document the OpenAPI document for reference resolution
*/
public static void processMultipartFormData(OpenApiMediaType mediaType,
CodegenJavaMethod methodTemplate,
JaxRsProjectSettings settings,
Document document) {
if (mediaType.getSchema() != null) {
OpenApi31Schema schema = (OpenApi31Schema) mediaType.getSchema();
if (containsValue(schema.getType(), "object")) {
Map<String, Schema> properties = schema.getProperties();
if (properties != null) {
methodTemplate.getConsumes().add("MediaType.MULTIPART_FORM_DATA");
createIndividualFormFieldParams(methodTemplate, settings, document, properties, schema);
}
}
}
}

/**
* Adds individual form field parameters to the method template based on the provided schema properties.
*
* @param methodTemplate the method template to modify with form field parameters
* @param settings the project settings used for type mapping and configuration
* @param document the OpenAPI document used for reference resolution
* @param properties a map of property names to their corresponding schema definitions
* @param schema the parent schema containing details about required fields
*/
private static void createIndividualFormFieldParams(CodegenJavaMethod methodTemplate,
JaxRsProjectSettings settings,
Document document,
Map<String, Schema> properties,
OpenApi31Schema schema) {
for (Map.Entry<String, Schema> property : properties.entrySet()) {
String fieldName = property.getKey();
Schema fieldSchema = property.getValue();

CodegenJavaArgument cgArgument = createFormFieldArgument(fieldName, fieldSchema, schema, settings, document);
methodTemplate.getArguments().add(cgArgument);
}
}

/**
* Creates a CodegenJavaArgument for a form field based on the schema property.
*
* @param fieldName the name of the form field
* @param fieldSchema the schema definition for the field
* @param parentSchema the parent schema containing the required field list
* @param settings the project settings for type mapping
* @param document the OpenAPI document for reference resolution
* @return the configured CodegenJavaArgument
*/
private static CodegenJavaArgument createFormFieldArgument(String fieldName, Schema fieldSchema, OpenApi31Schema parentSchema, JaxRsProjectSettings settings, Document document) {
CodegenJavaArgument cgArgument = new CodegenJavaArgument();
cgArgument.setName(fieldName);
cgArgument.setIn("form");

boolean isRequired = checkIfFieldIsRequired(fieldName, parentSchema);
cgArgument.setRequired(isRequired);

defineArgumentTypeForSchema(cgArgument, fieldSchema, settings, document);
return cgArgument;
}

/**
* Checks whether a specified field is marked as required in the given parent schema.
*
* @param fieldName the name of the field to check
* @param parentSchema the parent schema containing the list of required fields
* @return true if the field is marked as required, false otherwise
*/
private static boolean checkIfFieldIsRequired(String fieldName, OpenApi31Schema parentSchema) {
boolean isRequired = false;
if (parentSchema != null && parentSchema.getRequired() != null) {
isRequired = parentSchema.getRequired().contains(fieldName);
}
return isRequired;
}

/**
* Defines the appropriate type and format for the argument based on the schema definition.
*
* @param argument the argument to configure
* @param fieldSchema the schema to analyze
* @param settings the project settings for type mapping
* @param document the OpenAPI document for reference resolution
*/
private static void defineArgumentTypeForSchema(CodegenJavaArgument argument,
Schema fieldSchema,
JaxRsProjectSettings settings,
Document document) {
if (!validateSchemaAndSetDefaults(argument, fieldSchema)) {
return;
}
OpenApi31Schema oas31Schema = (OpenApi31Schema) fieldSchema;
if (resolveSchemaReference(argument, oas31Schema, settings, document)) {
return;
}
if (handleArrayType(argument, oas31Schema, settings, document)) {
return;
}
mapSchemaTypeToJavaType(argument, oas31Schema);
}

private static boolean validateSchemaAndSetDefaults(CodegenJavaArgument argument, Schema fieldSchema) {
if (fieldSchema == null) {
argument.setType(Collections.singletonList(JAVA_LANG_STRING));
return false;
}
if (!(fieldSchema instanceof OpenApi31Schema)) {
argument.setType(Collections.singletonList(JAVA_LANG_STRING));
return false;
}
return true;
}

private static boolean resolveSchemaReference(CodegenJavaArgument argument, OpenApi31Schema oas31Schema, JaxRsProjectSettings settings, Document document) {
String ref = oas31Schema.get$ref();
if (ref != null) {
String className = CodegenUtil.schemaRefToFQCN(settings, document, ref, settings.getJavaPackage() + ".beans");
argument.setType(Collections.singletonList(className));
return true;
}
return false;
}

private static boolean mapStringType(CodegenJavaArgument argument, OpenApi31Schema oas31Schema) {
if (!containsValue(oas31Schema.getType(), "string")) {
return false;
}
String format = oas31Schema.getFormat();
if ("binary".equals(format)) {
argument.setType(Collections.singletonList(JBOSS_FILE_UPLOAD));
argument.setFormat("binary");
} else if ("date".equals(format)) {
argument.setType(Collections.singletonList(JAVA_TIME_LOCAL_DATE));
argument.setFormat("date");
} else if ("date-time".equals(format)) {
argument.setType(Collections.singletonList(JAVA_TIME_LOCAL_DATE_TIME));
argument.setFormat("date-time");
} else {
argument.setType(Collections.singletonList(JAVA_LANG_STRING));
}
return true;
}

private static boolean mapIntegerType(CodegenJavaArgument argument, OpenApi31Schema oas31Schema) {
if (!containsValue(oas31Schema.getType(), "integer")) {
return false;
}
String format = oas31Schema.getFormat();
if ("int64".equals(format) || "long".equals(format)) {
argument.setType(Collections.singletonList(JAVA_LANG_LONG));
} else {
argument.setType(Collections.singletonList(JAVA_LANG_INTEGER));
}
if (format != null) {
argument.setFormat(format);
}
return true;
}

private static boolean mapNumberType(CodegenJavaArgument argument, OpenApi31Schema oas31Schema) {
if (!containsValue(oas31Schema.getType(), "number")) {
return false;
}

String format = oas31Schema.getFormat();
if ("double".equals(format)) {
argument.setType(Collections.singletonList(JAVA_LANG_DOUBLE));
} else if ("float".equals(format)) {
argument.setType(Collections.singletonList(JAVA_LANG_FLOAT));
} else {
argument.setType(Collections.singletonList(JAVA_MATH_BIG_DECIMAL));
}
if (format != null) {
argument.setFormat(format);
}
return true;
}


private static boolean mapBooleanType(CodegenJavaArgument argument, OpenApi31Schema oas31Schema) {
if (!containsValue(oas31Schema.getType(), "boolean")) {
return false;
}
argument.setType(Collections.singletonList(JAVA_LANG_BOOLEAN));
return true;
}

private static void mapObjectOrFallbackType(CodegenJavaArgument argument, OpenApi31Schema oas31Schema) {
if (containsValue(oas31Schema.getType(), "object") || oas31Schema.getType() == null) {
argument.setType(Collections.singletonList(JAVA_UTIL_MAP + "<" + JAVA_LANG_STRING + ", " + JAVA_LANG_OBJECT + ">"));
} else {
// Fallback to String for unknown types
argument.setType(Collections.singletonList(JAVA_LANG_STRING));
}
}

private static boolean handleArrayType(CodegenJavaArgument argument, OpenApi31Schema oas31Schema, JaxRsProjectSettings settings, Document document) {
if (!containsValue(oas31Schema.getType(), "array")) {
return false;
}
if (oas31Schema.getItems() != null) {
CodegenJavaArgument itemArgument = new CodegenJavaArgument();
defineArgumentTypeForSchema(itemArgument, oas31Schema.getItems(), settings, document);

List<String> itemTypes = itemArgument.getType();
if (itemTypes != null && !itemTypes.isEmpty()) {
String itemType = itemTypes.get(0);
argument.setType(Collections.singletonList(JAVA_UTIL_LIST + "<" + itemType + ">"));
} else {
argument.setType(Collections.singletonList(JAVA_UTIL_LIST + "<" + JAVA_LANG_STRING + ">"));
}
} else {
argument.setType(Collections.singletonList(JAVA_UTIL_LIST + "<" + JAVA_LANG_STRING + ">"));
}
return true;
}

private static void mapSchemaTypeToJavaType(CodegenJavaArgument argument, OpenApi31Schema oas31Schema) {
if (mapStringType(argument, oas31Schema)) {
return;
}
if (mapIntegerType(argument, oas31Schema)) {
return;
}
if (mapNumberType(argument, oas31Schema)) {
return;
}
if (mapBooleanType(argument, oas31Schema)) {
return;
}
mapObjectOrFallbackType(argument, oas31Schema);
}

}
Loading