Extension Authors Guide

Quarkus extensions add a new developer focused behavior to the core offering, and consist of two distinct parts, buildtime augmentation and runtime container. The augmentation part is responsible for all metadata processing, such as reading annotations, XML descriptors etc. The output of this augmentation phase is recorded bytecode which is responsible for directly instantiating the relevant runtime services.

This means that metadata is only processed once at build time, which both saves on startup time, and also on memory usage as the classes etc that are used for processing are not loaded (or even present) in the runtime JVM.

1. Extension philosophy

This section is a work in progress and gathers the philosophy under which extensions should be designed and written.

1.1. Why an extension framework

Quarkus’s mission is to transform your entire application including the libraries it uses, into an artifact optimized for GraalVM. To do this you need to analyze and understand the full "closed world" of the application. Without the full and complete context, the best that can be achieved is partial and limited generic support. By using the Quarkus extension approach, we can bring Java applications in line with memory footprint constrained environments like Kubernetes or cloud platforms.

The Quarkus extension framework results in significantly improved resource utilization even when GraalVM is not used (e.g. in HotSpot). Let’s list the actions an extension performs:

  • Gather build time metadata and generate code

    • This part has nothing to do with GraalVM, it is how Quarkus starts frameworks “at build time”

    • The extension framework facilitates reading metadata, scanning classes as well as generating classes as needed

    • A small part of the extension work is executed at runtime via the generated classes, while the bulk of the work is done at build time (called deployment time)

  • Enforce opinionated and sensible defaults based on the close world view of the application (e.g. an application with no @Entity does not need to start Hibernate ORM)

  • An extension hosts Substrate VM code substitution so that libraries can run on GraalVM

    • Most changes are pushed upstream to help the underlying library run on GraalVM

    • Not all changes can be pushed upstream, extensions host Substrate VM substitutions - which is a form of code patching - so that libraries can run

  • Host Substrate VM code substitution to help dead code elimination based on the application needs

    • This is application dependant and cannot really be shared in the library itself

    • For example, Quarkus optimizes the Hibernate code because it knows it only needs a specific connection pool and cache provider

  • Send metadata to GraalVM for example classes in need of reflection

    • This information is not static per library (e.g. Hibernate) but the framework has the semantic knowledge and knows which classes need to have reflection (for example @Entity classes)

1.2. Favor build time work over runtime work

As much as possible favor doing work at build time (deployment part of the extension) as opposed to let the framework do work at startup time (runtime). The more is done there, the smaller Quarkus applications using that extension will be and the faster they will load.

1.3. How to expose configuration

Quarkus simplifies the most common usages. This means that its defaults might be different than the library it integrates.

To make the simple experience easiest, unify the configuration in application.properties via MicroProfile Config. Avoid library specific configuration files, or at least make them optional: e.g. persistence.xml for Hibernate ORM is optional.

Extensions should see the configuration holistically as a Quarkus application instead of focusing on the library experience. For example quarkus.database.url and friends are shared between extensions as defining a database access is a shared task (instead of a hibernate. property for example). The most useful configuration options should be exposed as quarkus.[extension]. instead of the natural namespace of the library. Less common properties can live in the library namespace.

To fully enable the close world assumptions that Quarkus can optimize best, it is better to consider configuration options as build time settled vs overridable at runtime. Of course properties like host, port, password should be overridable at runtime. But many properties like enable caching or setting the JDBC driver can safely require a rebuild of the application.

1.4. Expose your components via CDI

Since CDI is the central programming model when it comes to component composition, frameworks should expose producers that are easily consumable by user applications.
For example, Hibernate ORM exposes EntityManagerFactory and EntityManager beans, the connection pool exposes DataSource beans etc. An extension must generate these bean definitions at build time.

1.4.1. How to Override a Bean Defined by a Library/Quarkus Extension

The recommended approach is to mark your bean as a CDI @Alternative that is enabled using the @Priority annotation. Let’s show a simple example. Suppose we work on an imaginary "quarkus-parser" extension and we have a default bean implementation:

@Dependent
class Parser {

  String[] parse(String expression) {
    return expression.split("::");
  }
}

And our extension also consumes this parser:

@ApplicationScoped
class ParserService {

  @Inject
  Parser parser;

  //...
}

Now, if a user or even some other extension needs to override the default implementation of the Parser the simplest solution is to use CDI @Alternative + @Priority:

@Alternative (1)
@Priority(1) (2)
@Singleton
class MyParser extends Parser {

  String[] parse(String expression) {
    // my super impl...
  }
}
  1. MyParser is an alternative bean.

  2. Enables the alternative. The priority could be any number to override the default bean but if there are multiple alternatives the highest priority wins.

Note
CDI alternatives are only considered during injection and type-safe resolution. For example the default implementation would still receive observer notifications.

1.5. Some types of extensions

There exist multiple stereotypes of extension, let’s list a few.

Bare library running

This is the less sophisticated extension. It consists of a set of patches to make sure a library runs on GraalVM. If possible, contribute these patches upstream, not in extensions. Second best is to write Substrate VM substitutions, which are patches applied during native image compilation.

Get a framework running

A framework at runtime typically reads configuration, scan the classpath and classes for metadata (annotations, getters etc), build a metamodel on top of which it runs, find options via the service loader pattern, prepare invocation calls (reflection), proxy interfaces, etc.
These operations should be done at build time and the metamodel be passed to the template DSL that will generate classes are runtime and boot the framework.

Get a CDI portable extension running

The CDI portable extension model is very flexible. Too flexible to benefit from the build time boot promoted by Quarkus. Most extension we have seen do not make use of these extreme flexibilities capabilities. The way to port a CDI extension to Quarkus is to rewrite it as a Quarkus extension which will define the various beans at build time (deployment time in extension parley).

2. Technical aspect

2.1. Three Phases of Bootstrap and Quarkus Philosophy

There are three distinct bootstrap phases of a Quarkus app:

Augmentation

This is the first phase, and is done by the Build Step Processors. These processors have access to Jandex annotation information and can parse any descriptors and read annotations, but should not attempt to load any application classes. The output of these build steps is some recorded bytecode, using an extension of the ObjectWeb ASM project called Gizmo(ext/gizmo), that is used to actually bootstrap the application at runtime. Depending on the io.quarkus.deployment.annotations.ExecutionTime value of the @io.quarkus.deployment.annotations.Record annotation associated with the build step, the step may be run in a different JVM based on the following two modes.

Static Init

If bytecode is recorded with @Record(STATIC_INIT) then it will be executed from a static init method on the main class. For a native executable build, this code is executed in a normal JVM as part of the native build process, and any retained objects that are produced in this stage will be directly serialized into the native executable via an image mapped file. This means that if a framework can boot in this phase then it will have its booted state directly written to the image, and so the boot code does not need to be executed when the image is started.

There are some restrictions on what can be done in this stage as the Substrate VM disallows some objects in the native executable. For example you should not attempt to listen on a port or start threads in this phase.

In non-native pure JVM mode, there is no real difference between Static and Runtime Init, except that Static Init is always executed first. This mode benefits from the same build phase augmentation as native mode as the descriptor parsing and annotation scanning are done at build time and any associated class/framework dependencies can be removed from the build output jar. In servers like WildFly, deployment related classes such as XML parsers hang around for the life of the application, using up valuable memory. Quarkus aims to eliminate this, so that the only classes loaded at runtime are actually used at runtime.

As an example, the only reason that a Quarkus application should load an XML parser is if the user is using XML in their application. Any XML parsing of configuration should be done in the Augmentation phase.

Runtime Init

If bytecode is recorded with @Record(RUNTIME_INIT) then it is executed from the application’s main method. This code will be run on native executable boot. In general as little code as possible should be executed in this phase, and should be restricted to code that needs to open ports etc.

Pushing as much as possible into the @Record(STATIC_INIT) phase allows for two different optimizations:

  1. In both native executable and pure JVM mode this allows the app to start as fast as possible since processing was done during build time. This also minimizes the classes/native code needed in the application to pure runtime related behaviors.

  2. Another benefit with native executable mode is that Substrate can more easily eliminate features that are not used. If features are directly initialized via bytecode, Substrate can detect that a method is never called and eliminate that method. If config is read at runtime, Substrate cannot reason about the contents of the config and so needs to keep all features in case they are required.

2.2. Maven setup

Your extension project should be setup as a multi-module project with two submodules:

  1. A deployment time submodule that handles the build time processing and bytecode recording.

  2. A runtime submodule that contains the runtime behavior that will provide the extension behavior in the native executable or runtime JVM.

Your runtime artifact should depend on io.quarkus:quarkus-core, and possibly the runtime artifacts of other Quarkus modules if you want to use functionality provided by them. You will also need to include the io.quarkus:quarkus-bootstrap-maven-plugin to generate the Quarkus extension descriptor included into the runtime artifact, if you are using the Quarkus parent pom it will automatically inherit the correct configuration. Futhermore, you’ll need to configure the maven-compiler-plugin to detect the quarkus-extension-processor annotation processor.

Note
By convention the deployment time artifact has the -deployment suffix, and the runtime artifact has no suffix (and is what the end user adds to their project).
<dependencies>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-core</artifactId>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-bootstrap-maven-plugin</artifactId>
            <!-- Executions configuration can be inherited from quarkus-build-parent -->
            <executions>
                <execution>
                    <goals>
                        <goal>extension-descriptor</goal>
                    </goals>
                    <configuration>
                         <deployment>${project.groupId}:${project.artifactId}-deployment:${project.version}</deployment>
                   </configuration>
               </execution>
           </executions>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <annotationProcessorPaths>
                    <path>
                        <groupId>io.quarkus</groupId>
                        <artifactId>quarkus-extension-processor</artifactId>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>
Note
The above maven-compiler-plugin configuration requires version 3.5+.
Warning

Under no circumstances can the runtime module depend on a deployment artifact. This would result in pulling all the deployment time code into runtime scope, which defeats the purpose of having the split.

Your deployment time module should depend on io.quarkus:quarkus-core-deployment, your runtime artifact, and possibly the deployment artifacts of other Quarkus modules if you want to use functionality provided by them. You will also need to configure the maven-compiler-plugin to detect the quarkus-extension-processor annotation processor.

<dependencies>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-core-deployment</artifactId>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <annotationProcessorPaths>
                    <path>
                        <groupId>io.quarkus</groupId>
                        <artifactId>quarkus-extension-processor</artifactId>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

2.3. Build Step Processors

Work is done at deployment time by producing and consuming instances of io.quarkus.builder.item.BuildItem. This is done by creating a class that has method(s) annotated with io.quarkus.deployment.annotations.BuildStep. These classes can consume items by injection, and produce items by either returning them from the method or by injecting an instance of io.quarkus.deployment.annotations.BuildProducer for the produced type. These processors can also record bytecode invocations, which is mapped to a BuildItem transparently.

There are two distinct types of BuildItem, SimpleBuildItem and MultiBuildItem. SimpleBuildItem will only ever have a single instance created, while MultiBuildItem can have many instances.

Injection can be done either via field injection, or via method parameter injection. Injection is used to set up dependencies between build steps. For example if you inject a List<ServletBuildItem> your build step will not be called until all possible producers of ServletBuildItem have been called. Injected objects are only valid during a @BuildStep method invocation, once the method is complete they are no longer valid.

The following items are valid for injection:

  • SimpleBuildItem instances (at some point we may support Optional<SimpleBuildItem>, but it is not implemented yet)

  • List<? extends MultiBuildItem> instances

  • BuildProducer<? extends BuildItem> instances

If a method returns a BuildItem, or injects a BuildProducer it is considered to be a producer of that item type, while if it injects the item or list of items it is a consumer.

Note that a @BuildStep method will only be called if it produces something that another consumer or the final output requires. If there is no consumer for a particular item then it will not be produced. What is required will depend on the final target that is being produced, for example when running in developer mode the final output will not ask for Substrate-specific build items such as ReflectiveClassBuildItem so methods that only produce Substrate specific items will not be invoked.

Note that private methods and fields are not allowed, as injection is resolved at compile time via an annotation processor, and the resulting code does not have permission to inject private fields or invoke private methods.

BuildItem instances should be immutable, as the producer/consumer model does not allow for mutation to be correctly ordered. This is not enforced but failure to adhere to this can result in race conditions.

2.3.1. Capabilities

The @BuildStep annotation has a providesCapabilities property that can be used to provide capability information to other extensions about what is present in the current application. Capabilities are simply strings that are used to describe an extension. Capabilities should generally be named after an extensions root package, for example the transactions extension will provide io.quarkus.transactions.

To check if a capability is present you can inject the io.quarkus.deployment.Capabilities object and call isCapabilityPresent.

Capabilities should be used when checking for the presence of an extension rather than class path based checks.

2.3.2. Application Archives

The @BuildStep annotation can also register marker files that determine which archives on the class path are considered to be 'Application Archives', and will therefore get indexed. This is done via the applicationArchiveMarkers. For example the ArC extension registers META-INF/beans.xml, which means that all archives on the class path with a beans.xml file will be indexed.

2.4. Configuration

Configuration in Quarkus is based on SmallRye Config, an implementation of the MicroProfile Config specification. All of the standard features of MP-Config are supported; in addition, there are several extensions which are made available by the SmallRye Config project as well as by Quarkus itself.

The value of these properties is configured in a application.properties file that follows the MicroProfile config format.

Configuration of Quarkus extensions is injection-based, using annotations.

2.4.1. Configuration Keys

Leaf configuration keys are mapped to non-private fields via the @io.quarkus.runtime.annotations.ConfigItem annotation.

Note
Though the SmallRye Config project is used for implementation, the standard @ConfigProperty annotation does not have the same semantics that are needed to support configuration within extensions.

Configuration keys are normally derived from the field names that they are tied to. This is done by de-camel-casing the name and then joining the segments with hyphens (-). Some examples:

  • bindAddress becomes bind-address

  • keepAliveTime becomes keep-alive-time

  • requestDNSTimeout becomes request-dns-timeout

The name can also be explicitly specified by giving a name attribute to the @ConfigItem annotation.

Note
Though it is possible to override the configuration key name using the name attribute of @ConfigItem, normally this should only be done in cases where (for example) the configuration key name is the same as a Java keyword.

2.4.2. Configuration Value types

The type of the field with the @ConfigItem annotation determines the conversion that is applied to it. Quarkus extensions may use the full range of configuration types made available by SmallRye Config, which includes:

  • All primitive types and primitive wrapper types

  • String

  • Any type which has a constructor accepting a single argument of type String or CharSequence

  • Any type which has a static method named of which accepts a single argument of type String

  • Any type which has a static method named valueOf or parse which accepts a single argument of type CharSequence or String

  • A List or Optional of any of the above types

  • OptionalInt, OptionalLong, OptionalDouble

In addition, custom converters may be registered by build extensions using the io.quarkus.deployment.builditem.ConfigurationCustomConverterBuildItem class.

Though these implicit converters use reflection, Quarkus will automatically ensure that they are loaded at the appropriate time.

2.4.3. Configuration Groups

Configuration values are always collected into grouping classes which are marked with the @io.quarkus.runtime.annotations.ConfigGroup annotation. These classes contain a field for each key within its group. In addition, configuration groups can be nested.

2.4.4. Configuration Maps

A Map can be used for configuration at any position where a configuration group would be allowed. The key type of such a map must be String, and its value may be either a configuration group class or a valid leaf type. The configuration key segment following the map’s key segment will be used as the key for map values.

2.4.5. Configuration Roots

Configuration roots are configuration groups that appear in the root of the configuration tree. A configuration property’s full name is determined by joining the string quarkus. with the hyphenated name of the fields that form the path from the root to the leaf field. For example, if I define a configuration root group called ThreadPool, with a nested group in a field named sizing that in turn contains a field called minSize, the final configuration property will be called quarkus.thread-pool.sizing.min-size.

A configuration root’s name can be given with the name property, or it can be inferred from the class name. If the latter, then the configuration key will be the class name, minus any Config or Configuration suffix, broken up by camel-case, lowercased, and re-joined using hyphens (-).

Note: The current implementation is still using injection site to determine the root set, so to avoid migration problems, it is recommended that the injection site (field or parameter) have the same name as the configuration root class until this change is complete.

2.4.5.1. Configuration Root Phases

A configuration root dictates when its contained keys are read from configuration, and when they are available to applications. The phases defined by io.quarkus.runtime.annotations.ConfigPhase are as follows:

Phase name Read & avail. at build time Avail. at run time Read during static init Re-read during startup (native executable) Notes

BUILD_TIME

Appropriate for things which affect build.

BUILD_AND_RUN_TIME_FIXED

Appropriate for things which affect build and must be visible for run time code. Not read from config at run time.

RUN_TIME_STATIC

Not available at build, read on start in JVM mode, fixed in native executable mode.

RUN_TIME

Not available at build, read at start in all modes.

For all cases other than the BUILD_TIME case, the configuration root class and all of the configuration groups and types contained therein must be located in, or reachable from, the extension’s run time artifact. Configuration roots of phase BUILD_TIME may be located in or reachable from either of the extension’s run time or deployment artifacts.

2.4.6. Configuration Example

import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigGroup;

import java.io.File;
import java.util.logging.Level;

@ConfigGroup (1)
public class FileConfig {

    /**
     * Enable file logging.
     */
    @ConfigItem(defaultValue = "true")
    boolean enable;

    /**
     * The log format.
     */
    @ConfigItem(defaultValue = "%d{yyyy-MM-dd HH:mm:ss,SSS} %h %N[%i] %-5p [%c{1.}] (%t) %s%e%n")
    String format;

    /**
     * The file log level.
     */
    @ConfigItem(defaultValue = "ALL")
    Level level;

    /**
     * The file logging log level.
     */
    @ConfigItem(defaultValue = "quarkus.log")
    File path;

}

/**
 * Logging configuration.
 */
@ConfigRoot(phase = ConfigPhase.RUN_TIME) (2)
public class LogConfiguration {

    // ...

    /**
     * Configuration properties for the logging file handler.
     */
    FileConfig file;
}

public class LoggingProcessor {
    // ...

    /**
     * Logging configuration.
     */
    (3)
    LogConfiguration config;
}
  1. The FileConfig class is annotated with @ConfigGroup to indicate that this is an aggregate configuration object containing a collection of configurable properties, rather than being a simple configuration key type.

  2. The @ConfigRoot annotation indicates that this object is a configuration root group, whose property names will have a parent only of quarkus.. In this case the properties within the group will begin with quarkus.log.*.

  3. Here the LoggingProcessor injects a LogConfiguration instance automatically by detecting the @ConfigRoot annotation.

A corresponding application.properties file for the File values could be:

quarkus.log.file.enable=true
quarkus.log.file.level=DEBUG
quarkus.log.file.path=/tmp/debug.log

2.5. Bytecode Recording

One of the main outputs of the build process is recorded bytecode. This bytecode actually sets up the runtime environment. For example, in order to start Undertow, the resulting application will have some bytecode that directly registers all Servlet instances and then starts Undertow.

As writing bytecode directly is incredibly complex, this is instead done via bytecode recorders. At deployment time, invocations are made on proxy instances of template objects that contain the actual runtime logic, and these invocations are recorded, including the value of method parameters. Bytecode is then created to do these same invocations on the actual template object at runtime.

In more detail, a processor class from the extensions deployment module gathers the configuration information within a @BuildStep method that is also annotated with a @Record(STATIC_INIT) or @Record(RUNTIME_INIT) annotation along with injection of a @Template annotated class from the runtime module. A class annotated with @Template is known as a template because it provides a template of methods to configure a runtime service. The value of template that is injected into the deployment class is a proxy of the template, and any method invocations that are made will be recorded, and output as bytecode that will be run at application startup.

Methods on a template can return a value, which must be proxiable (if you want to return a non-proxiable item wrap it in io.quarkus.runtime.RuntimeValue). These proxies may not be invoked directly, however they can be passed into other template methods. This can be any template method, including from other @Record methods, so a common pattern is to produce BuildItem instances that wrap the results of these template invocations.

For instance, in order to make arbitrary changes to a Servlet deployment Undertow has a ServletExtensionBuildItem, which is a MultiBuildItem that wraps a ServletExtension instance. I can return a ServletExtension from a template in another module, and Undertow will consume it and pass it into the template method that starts Undertow.

At runtime the bytecode will be invoked in the order it is generated. This means that build step dependencies implicitly control the order that generated bytecode is run. In the example above we know that the bytecode that produces a ServletExtensionBuildItem will be run before the bytecode that consumes it.

2.5.1. RecorderContext

io.quarkus.deployment.recording.RecorderContext provides some convenience methods to enhance bytecode recording, this includes the ability to register creation functions for classes without no-arg constructors, to register an object substitution (basically a transformer from a non-serializable object to a serializable one and vice versa), and to create a class proxy. This interface can be directly injected as a method parameter into any @Record method.

Calling classProxy with a given class name will create a Class that can be passed into template methods, and at runtime will be substituted with the class whose name was passed in to classProxy. This is basically a convenience to avoid the need to explicitly load classes in the templates.

TODO: config integration

2.6. Contexts and Dependency Injection

2.6.1. Extension Points

As a CDI based runtime, Quarkus extensions often make CDI beans available as part of the extension behavior. However, Quarkus DI solution does not support CDI Portable Extensions. Instead, Quarkus extensions can make use of various Build Time Extension Points.

2.7. Testing Extensions

Testing of extensions should be done with the io.quarkus.test.QuarkusUnitTest runner. This runner allows for Arquillian-style tests that test specific functionalities. It is not intended for testing user applications, as this should be done via io.quarkus.test.junit.QuarkusTest. The main difference between these test runners is that QuarkusTest simply boots the application once at the start of the run, while QuarkusUnitTest deploys a custom Quarkus application for each test class.

These tests should be placed in the deployment module, if additional Quarkus modules are required for testing their deployment modules should also be added as test scoped dependencies.

Note that QuarkusUnitTest is in the quarkus-junit5-internal module.

An example test class may look like:

package io.quarkus.health.test;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.ArrayList;
import java.util.List;

import javax.enterprise.inject.Instance;
import javax.inject.Inject;

import org.eclipse.microprofile.health.Health;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import io.quarkus.test.QuarkusUnitTest;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.restassured.RestAssured;

public class FailingUnitTest {


    @RegisterExtension                                                                  // (1)
    static final QuarkusUnitTest config = new QuarkusUnitTest()
            .setArchiveProducer(() ->
                    ShrinkWrap.create(JavaArchive.class)                                // (2)
                            .addClasses(FailingHealthCheck.class)
                            .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")
            );
    @Inject                                                                             // (3)
    @Health
    Instance<HealthCheck> checks;

    @Test
    public void testHealthServlet() {
        RestAssured.when().get("/health").then().statusCode(503);                       // (4)
    }

    @Test
    public void testHealthBeans() {
        List<HealthCheck> check = new ArrayList<>();                                    // (5)
        for (HealthCheck i : checks) {
            check.add(i);
        }
        assertEquals(1, check.size());
        assertEquals(HealthCheckResponse.State.DOWN, check.get(0).call().getState());
    }
}
  1. This tells JUnit to use the Quarkus unit test runner

  2. This producer is used to build the application to be tested. It uses Shrinkwrap to create a JavaArchive to test

  3. It is possible to inject beans from our test deployment directly into the test case

  4. This method directly invokes the health check Servlet and verifies the response

  5. This method uses the injected health check bean to verify it is returning the expected result

If you want to test that an extension properly fails at build time, use the setExpectedException method:

package io.quarkus.hibernate.orm;

import io.quarkus.deployment.configuration.ConfigurationError;
import io.quarkus.test.QuarkusUnitTest;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

public class PersistenceAndQuarkusConfigTest {

    @RegisterExtension
    static QuarkusUnitTest runner = new QuarkusUnitTest()
            .setExpectedException(ConfigurationError.class)                     (1)
            .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
                    .addAsManifestResource("META-INF/some-persistence.xml", "persistence.xml")
                    .addAsResource("application.properties"));

    @Test
    public void testPersistenceAndConfigTest() {
        // should not be called, deployment exception should happen first:
        // it's illegal to have Hibernate configuration properties in both the
        // application.properties and in the persistence.xml
        Assertions.fail();
    }

}
  1. This tells JUnit that the Quarkus deployment should fail with a specific exception

2.8. Native Executable Support

There Quarkus provides a lot of build items that control aspects of the native executable build. This allows for extensions to programmatically perform tasks such as registering classes for reflection or adding static resources to the native executable. Some of these build items are listed below:

io.quarkus.deployment.builditem.substrate.SubstrateResourceBuildItem

Includes static resources into the native executable.

io.quarkus.deployment.builditem.substrate.RuntimeReinitializedClassBuildItem

A class that will be reinitialized at runtime by Substrate. This will result in the static initializer running twice.

io.quarkus.deployment.builditem.substrate.SubstrateSystemPropertyBuildItem

A system property that will be set at native executable build time.

io.quarkus.deployment.builditem.substrate.SubstrateResourceBundleBuildItem

Includes a resource bundle in the native executable.

io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem

Registers a class for reflection in Substrate. Constructors are always registered, while methods and fields are optional.

io.quarkus.deployment.builditem.substrate.RuntimeInitializedClassBuildItem

A class that will be initialized at runtime rather than build time. This will cause the build to fail if the class is initialized as part of the native executable build process, so care must be taken.

io.quarkus.deployment.builditem.substrate.SubstrateConfigBuildItem

A convenience feature that allows you to control most of the above features from a single build item.

2.9. IDE support tips

2.9.1. Writing Quarkus extensions in Eclipse

The only particular aspect of writing Quarkus extensions in Eclipse is that APT (Annotation Processing Tool) is required as part of extension builds, which means you need to:

  • Install m2e-apt from https://marketplace.eclipse.org/content/m2e-apt

  • Define this property in your pom.xml: <m2e.apt.activation>jdt_apt</m2e.apt.activation>, although if you rely on io.quarkus:quarkus-build-parent you will get it for free.

  • If you have the io.quarkus:quarkus-extension-processor project open at the same time in your IDE (for example, if you have the Quarkus sources checked out and open in your IDE) you will need to close that project. Otherwise, Eclipse will not invoke the APT plugin that it contains.

  • If you just closed the extension processor project, be sure to do Maven > Update Project on the other projects in order for Eclipse to pick up the extension processor from the Maven repository.

2.10. Troubleshooting / Debugging Tips

2.10.1. Saving Application Generated Classes to Disk

The class augmentation step of Quarkus generates classes for various purposes. Sometimes you need to view these classes/bytecode to debug or understand an issue. Classes that are related to application augmentation are currently held in memory in a runtime class loader. To have these classes written out to disk for inspection, specify the quarkus.debug.generated-classes-dir system property, for example:

./mvnw clean install -Dquarkus.debug.generated-classes-dir=./target/app-generated-classes

2.11. Sample Test Extension

We have an extension that is used to test for regressions in the extension processing. It is located in https://github.com/quarkusio/quarkus/tree/master/core/test-extension directory. In this section we touch on some of the tasks an extension author will typically need to perform using the test-extension code to illustrate how the task could be done.

2.11.1. Features and Capabilities

When you start a Quarkus instance, you will see a line like the one highlighted in this example:

Example Startup Lines
2019-03-22 14:02:37,884 INFO  [io.quarkus] (main) Quarkus 999-SNAPSHOT started in 0.061s.
2019-03-22 14:02:37,884 INFO  [io.quarkus] (main) Installed features: [cdi, test-extension] (1)
  1. A list of features installed in the runtime image

The features listed reflect the types of extensions that are installed. An extension declares its display name using a Build Step Processors method that produces a FeatureBuildItem like this TestProcessor#featureBuildItem() method example:

TestProcessor#featureBuildItem()
    /**
     * Register a extension capability and feature
     *
     * @return test-extension feature build item
     */
    @BuildStep(providesCapabilities = "io.quarkus.test-extension")
    FeatureBuildItem featureBuildItem() {
        return new FeatureBuildItem("test-extension");
    }

The feature name should map to a label in the extension’s devtools/common/src/main/filtered/extensions.json entry so that the feature name displayed by the startup line matches a label that one can used to select the extension when creating a project using the Quarkus maven plugin as shown in this example taken from the Writing JSON REST Services guide where the "resteasy-jsonb" feature is referenced:

mvn io.quarkus:quarkus-maven-plugin:0.15.0:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=rest-json \
    -DclassName="org.acme.rest.json.FruitResource" \
    -Dpath="/fruits" \
    -Dextensions="resteasy-jsonb"

2.11.2. Bean Defining Annotations

The CDI layer processes CDI beans that are either explicitly registered or that it discovers based on bean defining annotations as defined in 2.5.1. Bean defining annotations. You can expand this set of annotations to include annotations your extension processes using a BeanDefiningAnnotationBuildItem as shown in this TestProcessor#registerBeanDefinningAnnotations example:

Register a Bean Definining Annotation
import javax.enterprise.context.ApplicationScoped;
import org.jboss.jandex.DotName;
import io.quarkus.extest.runtime.TestAnnotation;

public final class TestProcessor {
    static DotName TEST_ANNOTATION = DotName.createSimple(TestAnnotation.class.getName());
    static DotName TEST_ANNOTATION_SCOPE = DotName.createSimple(ApplicationScoped.class.getName());

...

    @BuildStep
    BeanDefiningAnnotationBuildItem registerX() {
        (1)
        return new BeanDefiningAnnotationBuildItem(TEST_ANNOTATION, TEST_ANNOTATION_SCOPE);
    }
...
}

/**
 * Marker annotation for test configuration target beans
 */
@Target({ TYPE })
@Retention(RUNTIME)
@Documented
@Inherited
public @interface TestAnnotation {
}

/**
 * A sample bean
 */
@TestAnnotation (2)
public class ConfiguredBean implements IConfigConsumer {

...
  1. Register the annotation class and CDI default scope using the Jandex DotName class.

  2. ConfiguredBean will be processed by the CDI layer the same as a bean annotated with the CDI standard @ApplicationScoped.

2.11.3. Parsing Config to Objects

One of the main things an extension is likely to do is completely separate the configuration phase of behavior from the runtime phase. Frameworks often do parsing/load of configuration on startup that can be done during build time to both reduce the runtime dependencies on frameworks like xml parsers as well as reducing the startup time the parsing incurs.

An example of parsing a XML config file using JAXB is shown in the TestProcessor#parseServiceXmlConfig method: .Parsing an XML Configuration into Runtime XmlConfig Instance

    @BuildStep
    @Record(STATIC_INIT)
    RuntimeServiceBuildItem parseServiceXmlConfig(TestTemplate template) throws JAXBException {
        RuntimeServiceBuildItem serviceBuildItem = null;
        JAXBContext context = JAXBContext.newInstance(XmlConfig.class);
        Unmarshaller unmarshaller = context.createUnmarshaller();
        InputStream is = getClass().getResourceAsStream("/config.xml"); (1)
        if (is != null) {
            log.infof("Have XmlConfig, loading");
            XmlConfig config = (XmlConfig) unmarshaller.unmarshal(is); (2)
...
        }
        return serviceBuildItem;
    }
  1. Look for a config.xml classpath resource

  2. If found, parse using JAXB context for XmlConfig.class

Note

If there was no /config.xml resource available in the build environment, then a null RuntimeServiceBuildItem would be returned and no subsequent logic based on a RuntimeServiceBuildItem being produced would execute.

Typically one is loading a configuration to create some runtime component/service as parseServiceXmlConfig is doing. We will come back to the rest of the behavior in parseServiceXmlConfig in the following Manage Non-CDI Service section.

If for some reason you need to parse the config and use it in other build steps in an extension processor, you would need to create an XmlConfigBuildItem to pass the parsed XmlConfig instance around.

Tip

If you look at the XmlConfig code you will see that it does carry around the JAXB annotations. If you don’t want these in the runtime image, you could clone the XmlConfig instance into some POJO object graph and then replace XmlConfig with the POJO class. We will do this in Replacing Classes in the Native Image.

2.11.4. Scanning Deployments Using Jandex

If your extension defines annotations or interfaces that mark beans needing to be processed, you can locate these beans using the Jandex API, a Java annotation indexer and offline reflection library. The following TestProcessor#scanForBeans method shows how to find the beans annotated with our @TestAnnotation that also implement the IConfigConsumer interface:

Example Jandex Usage
    static DotName TEST_ANNOTATION = DotName.createSimple(TestAnnotation.class.getName());
...

    @BuildStep
    @Record(STATIC_INIT)
    void scanForBeans(TestTemplate template, BeanArchiveIndexBuildItem beanArchiveIndex, (1)
            BuildProducer<TestBeanBuildItem> testBeanProducer) {
        IndexView indexView = beanArchiveIndex.getIndex(); (2)
        Collection<AnnotationInstance> testBeans = indexView.getAnnotations(TEST_ANNOTATION); (3)
        for (AnnotationInstance ann : testBeans) {
            ClassInfo beanClassInfo = ann.target().asClass();
            try {
                boolean isConfigConsumer = beanClassInfo.interfaceNames()
                        .stream()
                        .anyMatch(dotName -> dotName.equals(DotName.createSimple(IConfigConsumer.class.getName()))); (4)
                if (isConfigConsumer) {
                    Class<IConfigConsumer> beanClass = (Class<IConfigConsumer>) Class.forName(beanClassInfo.name().toString());
                    testBeanProducer.produce(new TestBeanBuildItem(beanClass)); (5)
                    log.infof("Configured bean: %s", beanClass);
                }
            } catch (ClassNotFoundException e) {
                log.warn("Failed to load bean class", e);
            }
        }
    }
  1. Depend on a BeanArchiveIndexBuildItem to have the build step be run after the deployment has been indexed.

  2. Retrieve the index.

  3. Find all beans annotated with @TestAnnotation.

  4. Determine which of these beans also has the IConfigConsumer interface.

  5. Save the bean class in a TestBeanBuildItem for use in a latter RUNTIME_INIT build step that will interact with the bean instances.

2.11.5. Interacting With Extension Beans

You can use the io.quarkus.arc.runtime.BeanContainer interface to interact with your extension beans. The following configureBeans methods illustrate interacting with the beans scanned for in the previous section:

Using CDI BeanContainer Interface
// TestProcessor#configureBeans
    @BuildStep
    @Record(RUNTIME_INIT)
    void configureBeans(TestTemplate template, List<TestBeanBuildItem> testBeans, (1)
            BeanContainerBuildItem beanContainer, (2)
            TestRunTimeConfig runTimeConfig) {

        for (TestBeanBuildItem testBeanBuildItem : testBeans) {
            Class<IConfigConsumer> beanClass = testBeanBuildItem.getConfigConsumer();
            template.configureBeans(beanContainer.getValue(), beanClass, buildAndRunTimeConfig, runTimeConfig); (3)
        }
    }

// TestTemplate#configureBeans
    public void configureBeans(BeanContainer beanContainer, Class<IConfigConsumer> beanClass,
            TestBuildAndRunTimeConfig buildTimeConfig,
            TestRunTimeConfig runTimeConfig) {
        log.infof("Begin BeanContainerListener callback\n");
        IConfigConsumer instance = beanContainer.instance(beanClass); (4)
        instance.loadConfig(buildTimeConfig, runTimeConfig); (5)
        log.infof("configureBeans, instance=%s\n", instance);
    }
  1. Consume the `TestBeanBuildItem`s produced from the scanning build step.

  2. Consume the BeanContainerBuildItem to order this build step to run after the CDI bean container has been created.

  3. Call the runtime template to record the bean interactions.

  4. Runtime template retrieves the bean using its type.

  5. Runtime template invokes the IConfigConsumer#loadConfig(…​) method passing in the configuration objects with runtime information.

2.11.6. Manage Non-CDI Service

A common purpose for an extension is to integrate a non-CDI aware service into the CDI based Quarkus runtime. Step 1 of this task is to load any configuration needed in a STATIC_INIT build step as we did in Parsing Config to Objects. Now we need to create an instance of the service using the configuration. Let’s return to the TestProcessor#parseServiceXmlConfig method to see how this can be done.

Creating a Non-CDI Service
// TestProcessor#parseServiceXmlConfig
    @BuildStep
    @Record(STATIC_INIT)
    RuntimeServiceBuildItem parseServiceXmlConfig(TestTemplate template) throws JAXBException {
        RuntimeServiceBuildItem serviceBuildItem = null;
        JAXBContext context = JAXBContext.newInstance(XmlConfig.class);
        Unmarshaller unmarshaller = context.createUnmarshaller();
        InputStream is = getClass().getResourceAsStream("/config.xml");
        if (is != null) {
            log.infof("Have XmlConfig, loading");
            XmlConfig config = (XmlConfig) unmarshaller.unmarshal(is);
            log.infof("Loaded XmlConfig, creating service");
            RuntimeValue<RuntimeXmlConfigService> service = template.initRuntimeService(config); //(1)
            serviceBuildItem = new RuntimeServiceBuildItem(service); //(3)
        }
        return serviceBuildItem;
    }

// TestTemplate#initRuntimeService
    public RuntimeValue<RuntimeXmlConfigService> initRuntimeService(XmlConfig config) {
        RuntimeXmlConfigService service = new RuntimeXmlConfigService(config); //(2)
        return new RuntimeValue<>(service);
    }

// RuntimeServiceBuildItem
    final public class RuntimeServiceBuildItem extends SimpleBuildItem {
    private RuntimeValue<RuntimeXmlConfigService> service;

    public RuntimeServiceBuildItem(RuntimeValue<RuntimeXmlConfigService> service) {
        this.service = service;
    }

    public RuntimeValue<RuntimeXmlConfigService> getService() {
        return service;
    }
}
  1. Call into the runtime template to record the creation of the service.

  2. Using the parsed XmlConfig instance, create an instance of RuntimeXmlConfigService and wrap it in a RuntimeValue. Use a RuntimeValue wrapper for non-interface objects that are non-proxiable.

  3. Wrap the return service value in a RuntimeServiceBuildItem for use in a RUNTIME_INIT build step that will start the service.

2.11.6.1. Starting a Service

Now that you have recorded the creation of a service during the build phase, you need to record how to start the service at runtime during booting. You do this with a RUNTIME_INIT build step as shown in the TestProcessor#startRuntimeService method.

Starting/Stopping a Non-CDI Service
// TestProcessor#startRuntimeService
    @BuildStep
    @Record(RUNTIME_INIT)
    ServiceStartBuildItem startRuntimeService(TestTemplate template, ShutdownContextBuildItem shutdownContextBuildItem , // (1)
            RuntimeServiceBuildItem serviceBuildItem) throws IOException { // (2)
        if (serviceBuildItem != null) {
            log.info("Registering service start");
            template.startRuntimeService(shutdownContextBuildItem, serviceBuildItem.getService()); // (3)
        } else {
            log.info("No RuntimeServiceBuildItem seen, check config.xml");
        }
        return new ServiceStartBuildItem("RuntimeXmlConfigService"); //(4)
    }

// TestTemplate#startRuntimeService
    public void startRuntimeService(ShutdownContext shutdownContext, RuntimeValue<RuntimeXmlConfigService> runtimeValue)
            throws IOException {
        RuntimeXmlConfigService service = runtimeValue.getValue();
        service.startService(); //(5)
        shutdownContext.addShutdownTask(service::stopService); //(6)
    }
  1. We consume a ShutdownContextBuildItem to register the service shutdown.

  2. We consume the previously initialized service captured in RuntimeServiceBuildItem.

  3. Call the runtime template to record the service start invocation.

  4. Produce a ServiceStartBuildItem to indicate the startup of a service. See Startup and Shutdown Events for details.

  5. Runtime template retrieves the service instance reference and calls its startService method.

  6. Runtime template registers an invocation of the service instance stopService method with the Quarkus ShutdownContext.

The code for the RuntimeXmlConfigService can be viewed here: {quarkus-blob-url}/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/RuntimeXmlConfigService.java[RuntimeXmlConfigService.java]

The testcase for validating that the RuntimeXmlConfigService has started can be found in the testRuntimeXmlConfigService test of ConfiguredBeanTest and NativeImageIT.

2.11.7. Startup and Shutdown Events

The Quarkus container supports startup and shutdown lifecycle events to notify components of the container startup and shutdown. There are CDI events fired that components can observe are illustrated in this example:

Observing Container Startup
import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.StartupEvent;

puclic class SomeBean {
    /**
     * Called when the runtime has started
     * @param event
     */
    void onStart(@Observes StartupEvent event) { // (1)
        System.out.printf("onStart, event=%s%n", event);
    }

    /**
     * Called when the runtime is shutting down
     * @param event
    */
    void onStop(@Observes ShutdownEvent event) { // (2)
        System.out.printf("onStop, event=%s%n", event);
    }
}
  1. Observe a StartupEvent to be notified the runtime has started.

  2. Observe a 'ShutdownEvent` to be notified when the runtime is going to shutdown.

What is the relevance of startup and shutdown events for extension authors? We have already seen the use of a ShutdownContext to register a callback to perform shutdown tasks in the Starting a Service section. These shutdown tasks would be called after a ShutdownEvent had been sent.

A StartupEvent is fired after all io.quarkus.deployment.builditem.ServiceStartBuildItem producers have been consumed. The implication of this is that if an extension has services that application components would expect to have been started when they observe a StartupEvent, the build steps that invoke the runtime code to start those services needs to produce a ServiceStartBuildItem to ensure that the runtime code is run before the StartupEvent is sent. Recall that we saw the production of a ServiceStartBuildItem in the previous section, and it is repeated here for clarity:

Example of Producing a ServiceStartBuildItem
// TestProcessor#startRuntimeService
    @BuildStep
    @Record(RUNTIME_INIT)
    ServiceStartBuildItem startRuntimeService(TestTemplate template, ShutdownContextBuildItem shutdownContextBuildItem,
            RuntimeServiceBuildItem serviceBuildItem) throws IOException {
...
        return new ServiceStartBuildItem("RuntimeXmlConfigService"); //(1)
    }
  1. Produce a ServiceStartBuildItem to indicate that this is a service starting step that needs to run before the StartupEvent is sent.

2.11.8. Register Resources for Use in Native Image

Not all configuration or resources can be consumed at build time. If you have classpath resources that the runtime needs to access, you need to inform the build phase that these resources need to be copied into the native image. This is done by producing one or more SubstrateResourceBuildItem or SubstrateResourceBundleBuildItem in the case of resource bundles. Examples of this are shown in this sample registerNativeImageReources build step:

Registering Resources and ResourceBundles
public final class MyExtProcessor {
    @Inject
    BuildProducer<SubstrateResourceBuildItem> resource;
    @Inject
    BuildProducer<SubstrateResourceBundleBuildItem> resourceBundle;

    @BuildStep
    void registerNativeImageReources() {
        resource.produce(new SubstrateResourceBuildItem("/security/runtime.keys")); //(1)

        resource.produce(new SubstrateResourceBuildItem(
                "META-INF/services/" + io.quarkus.SomeService.class.getName())); //(2)

        resourceBundle.produce(new SubstrateResourceBuildItem("javax.xml.bind.Messages")); //(3)
    }
}
  1. Indicate that the /security/runtime.keys classpath resource should be copied into native image.

  2. Indicate that the io.quarkus.SomeService ServiceLoader resource should be copied into native image.

  3. Indicate that the "javax.xml.bind.Messages" resource bundle should be copied into native image.

2.11.9. Object Substitution

Objects created during the build phase that are passed into the runtime need to have a default constructor in order for them to be created and configured at startup of the runtime from the build time state. If an object does not have a default constructor you will see an error similar to the following during generation of the augmented artifacts:

DSAPublicKey Serialization Error
	[error]: Build step io.quarkus.deployment.steps.MainClassBuildStep#build threw an exception: java.lang.RuntimeException: Unable to serialize objects of type class sun.security.provider.DSAPublicKeyImpl to bytecode as it has no default constructor
	at io.quarkus.builder.Execution.run(Execution.java:123)
	at io.quarkus.builder.BuildExecutionBuilder.execute(BuildExecutionBuilder.java:136)
	at io.quarkus.deployment.QuarkusAugmentor.run(QuarkusAugmentor.java:110)
	at io.quarkus.runner.RuntimeRunner.run(RuntimeRunner.java:99)
	... 36 more

There is a io.quarkus.runtime.ObjectSubstitution interface that can be implemented to tell Quarkus how to handle such classes. An example implementation for the DSAPublicKey is shown here:

DSAPublicKeyObjectSubstitution Example
package io.quarkus.extest.runtime.subst;

import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.DSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.logging.Logger;

import io.quarkus.runtime.ObjectSubstitution;

public class DSAPublicKeyObjectSubstitution implements ObjectSubstitution<DSAPublicKey, KeyProxy> {
    private static final Logger log = Logger.getLogger("DSAPublicKeyObjectSubstitution");
    @Override
    public KeyProxy serialize(DSAPublicKey obj) { //(1)
        log.info("DSAPublicKeyObjectSubstitution.serialize");
        byte[] encoded = obj.getEncoded();
        KeyProxy proxy = new KeyProxy();
        proxy.setContent(encoded);
        return proxy;
    }

    @Override
    public DSAPublicKey deserialize(KeyProxy obj) { //(2)
        log.info("DSAPublicKeyObjectSubstitution.deserialize");
        byte[] encoded = obj.getContent();
        X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encoded);
        DSAPublicKey dsaPublicKey = null;
        try {
            KeyFactory kf = KeyFactory.getInstance("DSA");
            dsaPublicKey = (DSAPublicKey) kf.generatePublic(publicKeySpec);

        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            e.printStackTrace();
        }
        return dsaPublicKey;
    }
}
  1. The serialize method takes the object without a default constructor and creates a KeyProxy that contains the information necessary to recreate the DSAPublicKey.

  2. The deserialize method uses the KeyProxy to recreate the DSAPublicKey from its encoded form using the key factory.

An extension registers this substitution by producing an ObjectSubstitutionBuildItem as shown in this TestProcessor#loadDSAPublicKey fragment:

Registering an Object Subtitution
    @BuildStep
    @Record(STATIC_INIT)
    PublicKeyBuildItem loadDSAPublicKey(TestTemplate template,
            BuildProducer<ObjectSubstitutionBuildItem> substitutions) throws IOException, GeneralSecurityException {
...
        // Register how to serialize DSAPublicKey
        ObjectSubstitutionBuildItem.Holder<DSAPublicKey, KeyProxy> holder = new ObjectSubstitutionBuildItem.Holder(
                DSAPublicKey.class, KeyProxy.class, DSAPublicKeyObjectSubstitution.class);
        ObjectSubstitutionBuildItem keysub = new ObjectSubstitutionBuildItem(holder);
        substitutions.produce(keysub);

        log.infof("loadDSAPublicKey run");
        return new PublicKeyBuildItem(publicKey);
    }

2.11.10. Replacing Classes in the Native Image

The Graal SDK supports substitutions of classes in the native image. An example of how one could replace the XmlConfig/XmlData classes with versions that have no JAXB annotation dependencies is shown in these example classes:

Substitution of XmlConfig/XmlData Classes Example
package io.quarkus.extest.runtime.graal;

import java.util.Date;

import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;

import io.quarkus.extest.runtime.config.XmlData;
@TargetClass(XmlConfig.class)
@Substitute
final public class Target_XmlConfig {
    @Substitute
    private String address;
    @Substitute
    private int port;
    @Substitute
    private ArrayList<XData> dataList;

    @Substitute
    public String getAddress() {
        return address;
    }

    @Substitute
    public int getPort() {
        return port;
    }

    @Substitute
    public ArrayList<XData> getDataList() {
        return dataList;
    }

    @Substitute
    @Override
    public String toString() {
        return "Target_XmlConfig{" +
                "address='" + address + '\'' +
                ", port=" + port +
                ", dataList=" + dataList +
                '}';
    }
}

@TargetClass(XmlData.class)
@Substitute
final public class Target_XmlData {
    @Substitute
    private String name;
    @Substitute
    private String model;
    @Substitute
    private Date date;

    @Substitute
    public String getName() {
        return name;
    }

    @Substitute
    public String getModel() {
        return model;
    }

    @Substitute
    public Date getDate() {
        return date;
    }

    @Substitute
    @Override
    public String toString() {
        return "Target_XmlData{" +
                "name='" + name + '\'' +
                ", model='" + model + '\'' +
                ", date='" + date + '\'' +
                '}';
    }

}