7

I'm building quarkus native and using Stripe sdk as external library. In order to support Stripe sdk it I needed to create reflection-config.json file and set in the application.properties quarkus.native.additional-build-args=-H:ReflectionConfigurationFiles=reflection-config.json

The reflection-config.json looks like so:

  {
    "name": "com.stripe.model.Customer",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allPublicFields": true
  },
  {
    "name": "com.stripe.model.Customer$InvoiceSettings",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allPublicFields": true
  },
  {
    "name": "com.stripe.model.StripeError",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allPublicFields": true
  },
  {
    "name": "com.stripe.model.PaymentIntent",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allPublicFields": true
  },
  {
    "name": "com.stripe.model.PaymentMethod",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allPublicFields": true
  }....

and so on. It contains too many classes. My question is if there is a way to set the whole package instead of tons of classes? For example:

  {
    "name": "com.stripe.model.*",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allPublicFields": true
  }

Didn't find any mention for it.

yanivsh
  • 293
  • 1
  • 5
  • 16

3 Answers3

2

See update below.

I'm having the exact same use case to programmatically add all classes of a package for reflection without writing an extension.
My goal is to add jOOQ generated DB classes for reflection so I can use them in a native compiled Quarkus GraalVM image with RESTEasy Reactive Jackson.
As there are a lot of these classes I don't really want to manually populate the reflection-config.json or the targets with a @RegisterForReflection annotation on a empty class.

I'm using Quarkus 2.7.0.Final (not io.quarkus.platform as it is not released yet) with Gradle 7.3.3 and tried the following, which unfortunately does not work. But I guess this only works when building extensions

package com.example.restapi.graal;

import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.IndexView;

public class JooqReflectionProcessor {

    private static final String JOOQ_DB_REFLECT_CLASSES = "com\\.example\\.restapi\\.db\\..+\\.tables\\.(pojos|records)\\..+";

    @BuildStep
    public void build(BuildProducer<ReflectiveClassBuildItem> reflectiveClass, CombinedIndexBuildItem indexBuildItem) {
        IndexView index = indexBuildItem.getIndex();
        for (ClassInfo clazz : index.getKnownClasses()) {
            if (clazz.name().toString().matches(JOOQ_DB_REFLECT_CLASSES)) {
                reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, clazz.name().toString()));
            }
        }
    }

}

Update:

It ain't pretty but I ended up writing a Gradle task which automatically creates the desired jOOQ reflection class with the @RegisterForReflection(targets = {...}) annotation:

task writeJooqReflectionClass {
    DefaultSourceDirectorySet sourceSet = project.sourceSets.findByName('main').java

    File jooqSrcDir = sourceSet.srcDirs
        .stream()
        .filter { it.path.replace('\\', '/').matches('.+src/generated/jooq/main') }
        .findFirst()
        .get()

    ArrayList<String> classesForReflection = sourceSet
        .filter {
            it.path.contains(jooqSrcDir.path) &&
                it.path.replace('\\', '/').matches('.+tables/(pojos|records)/.+') &&
                it.path.endsWith('.java')
        }
        .collect { it.path.replaceAll('[\\\\|/]', '.').substring(jooqSrcDir.path.length() + 1).replace('.java', '.class') }

    Collections.sort(classesForReflection)

    File file = new File("${jooqSrcDir.path}/com/example/restapi/db", "JooqReflectionConfig.java")
    file.getParentFile().mkdirs()
    file.text = """\
    package com.example.restapi.db;

    import io.quarkus.runtime.annotations.RegisterForReflection;

    @RegisterForReflection(targets = {
        ${String.join(',\n        ', classesForReflection)}
    })
    public class JooqReflectionConfig {

    }
    """.stripIndent()
}

compileJava.dependsOn(writeJooqReflectionClass)

For example produces Class com.example.restapi.db.JooqReflectionConfig with content:

package com.example.restapi.db;

import io.quarkus.runtime.annotations.RegisterForReflection;

@RegisterForReflection(targets = {
    com.example.restapi.db.master.tables.pojos.TableA.class,
    com.example.restapi.db.master.tables.pojos.TableB.class,
    com.example.restapi.db.master.tables.records.TableA.class,
    com.example.restapi.db.master.tables.records.TableB.class,
    com.example.restapi.db.mdc.tables.pojos.TableC.class,
    com.example.restapi.db.mdc.tables.pojos.TableD.class,
    com.example.restapi.db.mdc.tables.records.TableC.class,
    com.example.restapi.db.mdc.tables.records.TableD.class
})
public class JooqReflectionConfig {

}
ThoSap
  • 31
  • 8
1

You can do that with a Quarkus extension, get classes from the index and produce a ReflectiveClassBuildItem for all classes matching the package.

It's not that hard but requires a bit of work.

A less verbose alternative to what you're doing is to use @RegisterForReflection(targets = { ... }).

That's the only alternatives right now.

Guillaume Smet
  • 9,921
  • 22
  • 29
  • Is this still true in 2023? Can this function be used in Quarkus to loop over a whole package adding classes: https://www.graalvm.org/22.3/reference-manual/native-image/dynamic-features/Reflection/#configuration-with-features – Melloware Jun 18 '23 at 11:55
1

The cleanest solution seems to be writing a Quarkus extension.

Or, if you don't want to list RegisterForReflection targets manually, another possibility is to have reflection-config.json generated externally (probably using annotation processor? or maven/gradle task).