Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for readers and filters to access the Jandex index #1475

Merged
merged 2 commits into from
Jul 27, 2023
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
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,14 @@ jobs:
java-version: 11

- name: maven cache
uses: actions/cache@v3
uses: actions/cache/restore@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2

- name: generate javadocs
run: mvn -B package javadoc:javadoc -DskipTests
run: mvn -B install javadoc:javadoc -DskipTests

tck-reporting:
runs-on: ubuntu-latest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.openapi.OASFilter;
import org.eclipse.microprofile.openapi.OASModelReader;
import org.eclipse.microprofile.openapi.models.OpenAPI;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.Indexer;

import io.smallrye.openapi.api.OpenApiConfig;
import io.smallrye.openapi.api.OpenApiDocument;
Expand All @@ -27,6 +30,8 @@
*/
public class OpenApiProcessor {

static final IndexView EMPTY_INDEX = new Indexer().complete();

private OpenApiProcessor() {
}

Expand Down Expand Up @@ -71,8 +76,8 @@ public static OpenAPI bootstrap(OpenApiConfig config, IndexView index, ClassLoad
}
// Filter and model
if (config != null && classLoader != null) {
OpenApiDocument.INSTANCE.modelFromReader(modelFromReader(config, classLoader));
OpenApiDocument.INSTANCE.filter(getFilter(config, classLoader));
OpenApiDocument.INSTANCE.modelFromReader(modelFromReader(config, classLoader, index));
OpenApiDocument.INSTANCE.filter(getFilter(config, classLoader, index));
}

OpenApiDocument.INSTANCE.initialize();
Expand Down Expand Up @@ -169,20 +174,27 @@ public static OpenAPI modelFromAnnotations(OpenApiConfig config, ClassLoader loa
* @param config OpenApiConfig
* @param loader ClassLoader
* @return OpenApiImpl created from OASModelReader
*
* @deprecated use {@linkplain #modelFromReader(OpenApiConfig, ClassLoader, IndexView)} instead
*/
@Deprecated
public static OpenAPI modelFromReader(OpenApiConfig config, ClassLoader loader) {
String readerClassName = config.modelReader();
if (readerClassName == null) {
return null;
}
try {
Class<?> c = loader.loadClass(readerClassName);
OASModelReader reader = (OASModelReader) c.getDeclaredConstructor().newInstance();
return reader.buildModel();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
throw new OpenApiRuntimeException(e);
}
return modelFromReader(config, loader, EMPTY_INDEX);
}

/**
* Instantiate the configured {@link OASModelReader} and invoke it. If no reader is configured,
* then return null. If a class is configured but there is an error either instantiating or invoking
* it, a {@link OpenApiRuntimeException} is thrown.
*
* @param config OpenApiConfig
* @param loader ClassLoader
* @param index an IndexView to be provided to the filter when accepted via its constructor
* @return OpenApiImpl created from OASModelReader
*/
public static OpenAPI modelFromReader(OpenApiConfig config, ClassLoader loader, IndexView index) {
OASModelReader reader = newInstance(config.modelReader(), loader, index);
return reader != null ? reader.buildModel() : null;
}

/**
Expand All @@ -191,17 +203,49 @@ public static OpenAPI modelFromReader(OpenApiConfig config, ClassLoader loader)
* @param config OpenApiConfig
* @param loader ClassLoader
* @return OASFilter instance retrieved from loader
*
* @deprecated use {@linkplain #getFilter(OpenApiConfig, ClassLoader, IndexView)} instead
*/
@Deprecated
public static OASFilter getFilter(OpenApiConfig config, ClassLoader loader) {
String filterClassName = config.filter();
if (filterClassName == null) {
return getFilter(config, loader, EMPTY_INDEX);
}

/**
* Instantiate the {@link OASFilter} configured by the application.
*
* @param config OpenApiConfig
* @param loader ClassLoader
* @param index an IndexView to be provided to the filter when accepted via its constructor
* @return OASFilter instance retrieved from loader
*/
public static OASFilter getFilter(OpenApiConfig config, ClassLoader loader, IndexView index) {
return newInstance(config.filter(), loader, index);
}

@SuppressWarnings("unchecked")
static <T> T newInstance(String className, ClassLoader loader, IndexView index) {
if (className == null) {
return null;
}

Class<T> klazz = uncheckedCall(() -> (Class<T>) loader.loadClass(className));

return Arrays.stream(klazz.getDeclaredConstructors())
.filter(OpenApiProcessor::acceptsIndexView)
.findFirst()
.map(ctor -> uncheckedCall(() -> (T) ctor.newInstance(index)))
.orElseGet(() -> uncheckedCall(() -> klazz.getDeclaredConstructor().newInstance()));
}

private static boolean acceptsIndexView(Constructor<?> ctor) {
return ctor.getParameterCount() == 1 && IndexView.class.isAssignableFrom(ctor.getParameterTypes()[0]);
}

private static <T> T uncheckedCall(Callable<T> callable) {
try {
Class<?> c = loader.loadClass(filterClassName);
return (OASFilter) c.getDeclaredConstructor().newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
return callable.call();
} catch (Exception e) {
throw new OpenApiRuntimeException(e);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package io.smallrye.openapi.runtime;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.UUID;

import org.jboss.jandex.IndexView;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class OpenApiProcessorTest {

ClassLoader loader;

@BeforeEach
void setup() {
this.loader = Thread.currentThread().getContextClassLoader();
}

@Test
void testNewInstanceWithNullClassName() {
Object instance = OpenApiProcessor.newInstance(null, loader, OpenApiProcessor.EMPTY_INDEX);
assertNull(instance);
}

@Test
void testNewInstanceWithNotFoundClassName() {
String invalidClassName = UUID.randomUUID().toString();

Throwable thrown = assertThrows(OpenApiRuntimeException.class,
() -> OpenApiProcessor.newInstance(invalidClassName, loader, OpenApiProcessor.EMPTY_INDEX));
assertEquals(ClassNotFoundException.class, thrown.getCause().getClass());
}

@Test
void testNewInstanceWithEmptyIndex() {
IndexAwareObject instance = OpenApiProcessor.newInstance(IndexAwareObject.class.getName(), loader,
OpenApiProcessor.EMPTY_INDEX);
assertNotNull(instance);
assertFalse(instance.defaultConstructorUsed);
assertEquals(0, instance.index.getKnownClasses().size());
}

@Test
void testNewInstanceWithIndexUnsupported() {
IndexUnawareObject instance = OpenApiProcessor.newInstance(IndexUnawareObject.class.getName(), loader,
OpenApiProcessor.EMPTY_INDEX);
assertNotNull(instance);
assertTrue(instance.defaultConstructorUsed);
}

static class IndexAwareObject {
IndexView index;
boolean defaultConstructorUsed;

IndexAwareObject() {
defaultConstructorUsed = true;
}

IndexAwareObject(IndexView index) {
this.index = index;
defaultConstructorUsed = false;
}

IndexAwareObject(Object other) {
defaultConstructorUsed = false;
}
}

static class IndexUnawareObject {
boolean defaultConstructorUsed;

IndexUnawareObject() {
defaultConstructorUsed = true;
}

IndexUnawareObject(Object other) {
defaultConstructorUsed = false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,23 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.util.Collection;
import java.util.Optional;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;

import org.apache.commons.io.output.ByteArrayOutputStream;
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.openapi.models.OpenAPI;
import org.jboss.arquillian.container.spi.client.deployment.DeploymentDescription;
import org.jboss.arquillian.container.spi.event.container.BeforeDeploy;
import org.jboss.arquillian.core.api.annotation.Observes;
import org.jboss.jandex.DotName;
import org.jboss.jandex.Index;
import org.jboss.jandex.IndexWriter;
import org.jboss.jandex.Indexer;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ArchivePaths;
Expand Down Expand Up @@ -114,7 +117,7 @@ private static void generateOpenAPI(final WebArchive war) {
ClassLoader contextClassLoader = currentThread().getContextClassLoader();

Optional<OpenAPI> annotationModel = ofNullable(modelFromAnnotations(openApiConfig, contextClassLoader, index));
Optional<OpenAPI> readerModel = ofNullable(modelFromReader(openApiConfig, contextClassLoader));
Optional<OpenAPI> readerModel = ofNullable(modelFromReader(openApiConfig, contextClassLoader, index));
Optional<OpenAPI> staticFileModel = Stream.of(modelFromFile(openApiConfig, war, "/META-INF/openapi.json", JSON),
modelFromFile(openApiConfig, war, "/META-INF/openapi.yaml", YAML),
modelFromFile(openApiConfig, war, "/META-INF/openapi.yml", YAML))
Expand All @@ -128,7 +131,7 @@ private static void generateOpenAPI(final WebArchive war) {
annotationModel.ifPresent(document::modelFromAnnotations);
readerModel.ifPresent(document::modelFromReader);
staticFileModel.ifPresent(document::modelFromStaticFile);
document.filter(getFilter(openApiConfig, contextClassLoader));
document.filter(getFilter(openApiConfig, contextClassLoader, index));
document.initialize();
OpenAPI openAPI = document.get();

Expand All @@ -139,6 +142,13 @@ private static void generateOpenAPI(final WebArchive war) {
// Ignore
}

try (ByteArrayOutputStream indexOut = new ByteArrayOutputStream()) {
new IndexWriter(indexOut).write(index);
war.addAsManifestResource(new ByteArrayAsset(indexOut.toByteArray()), "jandex.idx");
} catch (IOException e) {
throw new UncheckedIOException(e);
}

document.reset();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static io.smallrye.openapi.runtime.io.Format.JSON;
import static io.smallrye.openapi.runtime.io.Format.YAML;

import java.io.InputStream;
import java.net.URL;
import java.util.Map;
import java.util.Optional;
Expand All @@ -17,6 +18,8 @@
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.openapi.models.OpenAPI;
import org.jboss.jandex.IndexReader;
import org.jboss.jandex.IndexView;
import org.jboss.resteasy.spi.ResteasyDeployment;

import io.smallrye.openapi.api.OpenApiConfig;
Expand Down Expand Up @@ -72,15 +75,21 @@ private Optional<OpenAPI> readOpenApiFile(final ServletContext servletContext, f
return Optional.empty();
}

final IndexView index;

try (InputStream indexStream = servletContext.getResourceAsStream("/META-INF/jandex.idx")) {
index = new IndexReader(indexStream).read();
}

final OpenApiDocument document = OpenApiDocument.INSTANCE;

try (OpenApiStaticFile staticFile = new OpenApiStaticFile(resource.openStream(), format)) {
Config config = ConfigProvider.getConfig();
OpenApiConfig openApiConfig = OpenApiConfig.fromConfig(config);
document.reset();
document.config(openApiConfig);
document.filter(OpenApiProcessor.getFilter(openApiConfig, Thread.currentThread().getContextClassLoader()));
document.modelFromStaticFile(
io.smallrye.openapi.runtime.OpenApiProcessor.modelFromStaticFile(openApiConfig, staticFile));
document.filter(OpenApiProcessor.getFilter(openApiConfig, Thread.currentThread().getContextClassLoader(), index));
document.modelFromStaticFile(OpenApiProcessor.modelFromStaticFile(openApiConfig, staticFile));
document.initialize();
return Optional.of(document.get());
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ private OpenApiDocument generateSchema(

OpenAPI staticModel = generateStaticModel(openApiConfig, resourcesSrcDirs);
OpenAPI annotationModel = generateAnnotationModel(index, openApiConfig, SmallryeOpenApiTask.class.getClassLoader());
OpenAPI readerModel = OpenApiProcessor.modelFromReader(openApiConfig, classLoader);
OpenAPI readerModel = OpenApiProcessor.modelFromReader(openApiConfig, classLoader, index);

OpenApiDocument document = OpenApiDocument.INSTANCE;

Expand All @@ -146,7 +146,7 @@ private OpenApiDocument generateSchema(
addingModelDebug("static", staticModel);
document.modelFromStaticFile(staticModel);
}
document.filter(OpenApiProcessor.getFilter(openApiConfig, classLoader));
document.filter(OpenApiProcessor.getFilter(openApiConfig, classLoader, index));
document.initialize();

return document;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ private OpenApiDocument generateSchema(IndexView index) throws IOException, Depe

OpenAPI staticModel = generateStaticModel(openApiConfig);
OpenAPI annotationModel = generateAnnotationModel(index, openApiConfig, classLoader);
OpenAPI readerModel = OpenApiProcessor.modelFromReader(openApiConfig, classLoader);
OpenAPI readerModel = OpenApiProcessor.modelFromReader(openApiConfig, classLoader, index);

OpenApiDocument document = OpenApiDocument.newInstance();

Expand All @@ -304,7 +304,7 @@ private OpenApiDocument generateSchema(IndexView index) throws IOException, Depe
if (staticModel != null) {
document.modelFromStaticFile(staticModel);
}
document.filter(OpenApiProcessor.getFilter(openApiConfig, classLoader));
document.filter(OpenApiProcessor.getFilter(openApiConfig, classLoader, index));
document.initialize();

return document;
Expand Down