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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ TODO
.interp
tmp
checkstyle
javadoc
*.mv.db
versions
out
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import org.gradle.api.tasks.TaskAction;

import edu.umd.cs.findbugs.annotations.Nullable;

import java.io.File;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -44,27 +46,27 @@ public void generate() throws Throwable {

String mainClass = Optional.ofNullable(this.mainClass)
.orElseGet(() -> computeMainClassName(projects));

Path outputDir = classes(getProject(), false);
// Reduce lookup to current project: See https://github.com/jooby-project/jooby/issues/2756
String metaInf =
outputDir
.resolve("META-INF")
.resolve("services")
.resolve("io.jooby.MvcFactory")
.toAbsolutePath()
.toString();
var sources = projects.stream()
.flatMap(project -> {
var sourceSet = sourceSet(project, false);
return sourceSet.stream()
.flatMap(it -> it.getAllSource().getSrcDirs().stream())
.map(File::toPath);
})
.distinct()
.toList(); Path outputDir = classes(getProject(), false);

ClassLoader classLoader = createClassLoader(projects);

getLogger().info("Generating OpenAPI: " + mainClass);
getLogger().debug("Using classloader: " + classLoader);
getLogger().debug("Output directory: " + outputDir);
getLogger().debug("META-INF: " + metaInf);
getLogger().debug("Source directories: " + sources);

OpenAPIGenerator tool = new OpenAPIGenerator(metaInf);
OpenAPIGenerator tool = new OpenAPIGenerator();
tool.setClassLoader(classLoader);
tool.setOutputDir(outputDir);
tool.setSources(sources);
trim(includes).ifPresent(tool::setIncludes);
trim(excludes).ifPresent(tool::setExcludes);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,23 +49,21 @@ protected void doExecute(@NonNull List<MavenProject> projects, @NonNull String m
throws Exception {
ClassLoader classLoader = createClassLoader(projects);
Path outputDir = Paths.get(project.getBuild().getOutputDirectory());
// Reduce lookup to current project: See https://github.com/jooby-project/jooby/issues/2756
String metaInf =
outputDir
.resolve("META-INF")
.resolve("services")
.resolve("io.jooby.MvcFactory")
.toAbsolutePath()
.toString();
var sources =
projects.stream()
.map(project -> Paths.get(project.getBuild().getSourceDirectory()))
.distinct()
.toList();

getLog().info("Generating OpenAPI: " + mainClass);
getLog().debug("Using classloader: " + classLoader);
getLog().debug("Output directory: " + outputDir);
getLog().debug("META-INF: " + metaInf);
getLog().debug("Source directories: " + sources);

OpenAPIGenerator tool = new OpenAPIGenerator(metaInf);
OpenAPIGenerator tool = new OpenAPIGenerator();
tool.setClassLoader(classLoader);
tool.setOutputDir(outputDir);
tool.setSources(sources);
trim(includes).ifPresent(tool::setIncludes);
trim(excludes).ifPresent(tool::setExcludes);

Expand Down
13 changes: 6 additions & 7 deletions modules/jooby-openapi/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@
<artifactId>swagger-models</artifactId>
</dependency>

<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.26.1</version>
</dependency>

<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
Expand Down Expand Up @@ -134,13 +140,6 @@
<version>1.17.6</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.26.1</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;

import io.jooby.Context;
import io.jooby.MediaType;
import io.jooby.Router;
import io.jooby.Session;
import io.jooby.*;
import io.jooby.annotation.ContextParam;
import io.jooby.annotation.CookieParam;
import io.jooby.annotation.FormParam;
Expand Down Expand Up @@ -251,9 +248,18 @@ public static List<OperationExt> parse(
return parse(ctx, prefix, type);
}
}
return Collections.emptyList();
return List.of();
}

/**
* This is the main entrypoint beside there is a public {@link #parse(ParserContext, String,
* Signature, MethodInsnNode)}.
*
* @param ctx
* @param prefix
* @param type
* @return
*/
public static List<OperationExt> parse(ParserContext ctx, String prefix, Type type) {
List<OperationExt> result = new ArrayList<>();
ClassNode classNode = ctx.classNode(type);
Expand All @@ -262,7 +268,75 @@ public static List<OperationExt> parse(ParserContext ctx, String prefix, Type ty
ctx.debugHandler(method);
result.addAll(routerMethod(ctx, prefix, classNode, method));
}
result.forEach(it -> it.setController(classNode));
var javaDocParser = ctx.javadoc();
for (OperationExt operationExt : result) {
operationExt.setController(classNode);
try {
var className = operationExt.getControllerName().replace("/", ".");
javaDocParser
.parse(className)
.ifPresent(
doc -> {
operationExt.setPathDescription(doc.getDescription());
operationExt.setPathSummary(doc.getSummary());
doc.getTags().forEach(operationExt::addTag);
if (!doc.getExtensions().isEmpty()) {
operationExt.setPathExtensions(doc.getExtensions());
}
var parameterNames =
Optional.ofNullable(operationExt.getNode().parameters)
.orElse(List.of())
.stream()
.map(p -> p.name)
.toList();
doc.getMethod(operationExt.getOperationId(), parameterNames)
.ifPresent(
methodDoc -> {
operationExt.setSummary(methodDoc.getSummary());
operationExt.setDescription(methodDoc.getDescription());
if (!methodDoc.getExtensions().isEmpty()) {
operationExt.setExtensions(methodDoc.getExtensions());
}
methodDoc.getTags().forEach(operationExt::addTag);
// Parameters
for (var parameterName : parameterNames) {
var paramExt =
operationExt.getParameters().stream()
.filter(p -> p.getName().equals(parameterName))
.findFirst()
.map(ParameterExt.class::cast)
.orElse(null);
var paramDoc = methodDoc.getParameterDoc(parameterName);
if (paramDoc != null) {
if (paramExt == null) {
operationExt.getRequestBody().setDescription(paramDoc);
} else {
paramExt.setDescription(paramDoc);
}
}
}
// return types
var defaultResponse = operationExt.getDefaultResponse();
if (defaultResponse != null) {
defaultResponse.setDescription(methodDoc.getReturnDoc());
}
for (var throwsDoc : methodDoc.getThrows().values()) {
var response =
operationExt.getResponse(
Integer.toString(throwsDoc.getStatusCode().value()));
if (response == null) {
response =
operationExt.addResponse(
Integer.toString(throwsDoc.getStatusCode().value()));
}
response.setDescription(throwsDoc.getText());
}
});
});
} catch (Exception x) {
throw SneakyThrows.propagate(x);
}
}
return result;
}

Expand Down Expand Up @@ -382,7 +456,7 @@ private static List<ParameterExt> routerArguments(

if (paramType == ParamType.BODY) {
RequestBodyExt body = new RequestBodyExt();
body.setRequired(required);
body.setRequired(true);
body.setJavaType(javaType);
requestBody.accept(body);
} else if (paramType == ParamType.FORM) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import java.util.Iterator;
import java.util.Set;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jooby.FileUpload;
import io.jooby.Jooby;
Expand All @@ -37,7 +36,7 @@ public ModelConverterExt(ObjectMapper mapper) {
@Override
public Schema resolve(
AnnotatedType type, ModelConverterContext context, Iterator<ModelConverter> chain) {
JavaType javaType = _mapper.getTypeFactory().constructType(type.getType());
var javaType = _mapper.getTypeFactory().constructType(type.getType());
if (javaType.isCollectionLikeType() || javaType.isArrayType()) {
if (isFile(javaType.getContentType().getRawClass())) {
return new ArraySchema().items(new FileSchema());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Jooby https://jooby.io
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
* Copyright 2014 Edgar Espina
*/
package io.jooby.internal.openapi;

import java.lang.reflect.Type;
import java.util.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.swagger.v3.core.converter.*;
import io.swagger.v3.core.util.ReferenceTypeUtils;
import io.swagger.v3.oas.models.media.Schema;

public class ModelConvertersExt extends ModelConverters {

/** Copy of {@link ModelConverterContextImpl} required for access to schemas by class name. */
private static class ModelConverterContextExt implements ModelConverterContext {
private static final Logger LOGGER = LoggerFactory.getLogger(ModelConverterContextExt.class);

private final List<ModelConverter> converters;
private final Map<String, Schema> modelByName;
private final HashMap<AnnotatedType, Schema> modelByType;
private final Set<AnnotatedType> processedTypes;

public ModelConverterContextExt(List<ModelConverter> converters) {
this.converters = converters;
modelByName = new TreeMap<>();
modelByType = new HashMap<>();
processedTypes = new HashSet<>();
}

public ModelConverterContextExt(ModelConverter converter) {
this(new ArrayList<ModelConverter>());
converters.add(converter);
}

@Override
public Iterator<ModelConverter> getConverters() {
return converters.iterator();
}

@Override
public void defineModel(String name, Schema model) {
AnnotatedType aType = null;
defineModel(name, model, aType, null);
}

@Override
public void defineModel(String name, Schema model, Type type, String prevName) {
defineModel(name, model, new AnnotatedType().type(type), prevName);
}

@Override
public void defineModel(String name, Schema model, AnnotatedType type, String prevName) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace(String.format("defineModel %s %s", name, model));
}
modelByName.put(name, model);

if (prevName != null && !prevName.isBlank() && !prevName.equals(name)) {
modelByName.remove(prevName);
}

if (type != null && type.getType() != null) {
modelByType.put(type, model);
}
}

@Override
public Map<String, Schema> getDefinedModels() {
return Collections.unmodifiableMap(modelByName);
}

@Override
public Schema resolve(AnnotatedType type) {
AnnotatedType aType = ReferenceTypeUtils.unwrapReference(type);
if (aType != null) {
return resolve(aType);
}

if (processedTypes.contains(type)) {
return modelByType.get(type);
} else {
processedTypes.add(type);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("resolve %s", type.getType()));
}
Iterator<ModelConverter> converters = this.getConverters();
Schema resolved = null;
if (converters.hasNext()) {
ModelConverter converter = converters.next();
LOGGER.trace("trying extension {}", converter);
resolved = converter.resolve(type, this, converters);
}
if (resolved != null) {
modelByType.put(type, resolved);

Schema resolvedImpl = resolved;
if (resolvedImpl.getName() != null) {
modelByName.put(resolvedImpl.getName(), resolved);
}
} else {
processedTypes.remove(type);
}

return resolved;
}
}

public ModelConvertersExt() {
super(false);
}

@Override
public ResolvedSchemaExt readAllAsResolvedSchema(Type type) {
return (ResolvedSchemaExt) super.readAllAsResolvedSchema(type);
}

@Override
public ResolvedSchemaExt readAllAsResolvedSchema(AnnotatedType type) {
return (ResolvedSchemaExt) super.readAllAsResolvedSchema(type);
}

@Override
public ResolvedSchemaExt resolveAsResolvedSchema(AnnotatedType type) {
var context = new ModelConverterContextExt(getConverters());
var resolvedSchema = new ResolvedSchemaExt();
resolvedSchema.schema = context.resolve(type);
resolvedSchema.referencedSchemas = context.getDefinedModels();
resolvedSchema.referencedSchemasByType = new HashMap<>();
context.modelByType.forEach(
(annotatedType, schema) ->
resolvedSchema.referencedSchemasByType.put(annotatedType.getType(), schema));
return resolvedSchema;
}
}
Loading
Loading