[GERONIMO-6796] OpenWebBeans integration
diff --git a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/ConfigurationGenerator.java b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/ConfigurationGenerator.java
index 106367c..c8a6b3f 100644
--- a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/ConfigurationGenerator.java
+++ b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/ConfigurationGenerator.java
@@ -16,10 +16,13 @@
*/
package org.apache.geronimo.arthur.impl.nativeimage.generator;
-import static java.util.Collections.singletonList;
-import static java.util.Optional.ofNullable;
-import static java.util.stream.Collectors.toList;
-import static java.util.stream.Collectors.toSet;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.geronimo.arthur.impl.nativeimage.ArthurNativeImageConfiguration;
+import org.apache.geronimo.arthur.spi.ArthurExtension;
+import org.apache.geronimo.arthur.spi.model.ClassReflectionModel;
+import org.apache.geronimo.arthur.spi.model.DynamicProxyModel;
+import org.apache.geronimo.arthur.spi.model.ResourcesModel;
import java.io.IOException;
import java.io.Writer;
@@ -30,6 +33,7 @@
import java.nio.file.StandardOpenOption;
import java.util.Collection;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -38,13 +42,12 @@
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import org.apache.geronimo.arthur.impl.nativeimage.ArthurNativeImageConfiguration;
-import org.apache.geronimo.arthur.spi.ArthurExtension;
-import org.apache.geronimo.arthur.spi.model.DynamicProxyModel;
-import org.apache.geronimo.arthur.spi.model.ResourcesModel;
-
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
+import static java.util.Collections.singletonList;
+import static java.util.Comparator.comparing;
+import static java.util.Optional.ofNullable;
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
@Slf4j
@RequiredArgsConstructor
@@ -103,7 +106,14 @@
log.info("Creating reflection model '{}'", json);
try (final Writer writer = Files.newBufferedWriter(
json, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
- jsonSerializer.accept(context.getReflections(), writer);
+ jsonSerializer.accept(
+ context.getReflections().stream()
+ .collect(groupingBy(ClassReflectionModel::getName))
+ .values().stream()
+ .map(this::merge)
+ .sorted(comparing(ClassReflectionModel::getName))
+ .collect(toList()),
+ writer);
}
context.addReflectionConfigFile(json.toAbsolutePath().toString());
}
@@ -167,6 +177,39 @@
}
}
+ private ClassReflectionModel merge(final List<ClassReflectionModel> classReflectionModels) {
+ final Iterator<ClassReflectionModel> modelIterator = classReflectionModels.iterator();
+ final ClassReflectionModel model = modelIterator.next();
+ while (modelIterator.hasNext()) {
+ final ClassReflectionModel next = modelIterator.next();
+ if (next.getAllDeclaredClasses()) {
+ model.setAllDeclaredClasses(true);
+ }
+ if (next.getAllDeclaredFields()) {
+ model.setAllDeclaredFields(true);
+ }
+ if (next.getAllDeclaredConstructors()) {
+ model.setAllDeclaredConstructors(true);
+ }
+ if (next.getAllDeclaredMethods()) {
+ model.setAllDeclaredMethods(true);
+ }
+ if (next.getAllPublicMethods()) {
+ model.setAllPublicMethods(true);
+ }
+ if (next.getAllPublicFields()) {
+ model.setAllPublicFields(true);
+ }
+ if (next.getAllPublicConstructors()) {
+ model.setAllPublicConstructors(true);
+ }
+ if (next.getAllPublicClasses()) {
+ model.setAllPublicClasses(true);
+ }
+ }
+ return model;
+ }
+
private void ensureWorkingDirectoryExists() throws IOException {
if (!Files.exists(workingDirectory)) {
Files.createDirectories(workingDirectory);
diff --git a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContext.java b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContext.java
index 6eb8f29..01dc457 100644
--- a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContext.java
+++ b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContext.java
@@ -19,6 +19,7 @@
import static java.util.Arrays.asList;
import static java.util.Optional.of;
import static java.util.Optional.ofNullable;
+import static java.util.stream.Collectors.toList;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
@@ -119,7 +120,9 @@
if (configuration.getInitializeAtBuildTime() == null) {
configuration.setInitializeAtBuildTime(new ArrayList<>());
}
- configuration.getInitializeAtBuildTime().addAll(asList(classes));
+ configuration.getInitializeAtBuildTime().addAll(Stream.of(classes)
+ .filter(it -> !configuration.getInitializeAtBuildTime().contains(it))
+ .collect(toList()));
}
@Override
@@ -156,7 +159,8 @@
@Override
public Optional<Predicate<String>> createPredicate(final String property, final ArthurExtension.PredicateType type) {
- return ofNullable(getProperty(property)).flatMap(ex -> Stream.of(ex.split(","))
+ return ofNullable(getProperty(property))
+ .flatMap(ex -> Stream.of(ex.split(","))
.map(String::trim)
.filter(it -> !it.isEmpty())
.map(it -> of((Predicate<String>) n -> type.test(it, n)))
diff --git a/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/ArthurMojo.java b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/ArthurMojo.java
index 89f08c6..669a78d 100644
--- a/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/ArthurMojo.java
+++ b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/ArthurMojo.java
@@ -180,8 +180,6 @@
}
private String buildDownloadUrl(final String graalPlatform) {
- // defaultValue = "https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-${graalSimpleVersion}/graalvm-ce-java${graalJavaVersion}-${githubPlatform}-${graalSimpleVersion}.tar.gz")
- // // defaultValue = "https://api.sdkman.io/2/broker/download/java/${graalVersion}-grl/${platform}")
if (graalDownloadUrl.startsWith("https://api.sdkman.io/2/broker/download/java/")) {
return graalDownloadUrl
.replace("${graalVersion}", graalVersion)
@@ -213,11 +211,4 @@
.orElseGet(() -> System.getProperty("os.arch", "64").replace("amd", "")))
.toLowerCase(ROOT);
}
-
- public static void main(String[] args) {
- System.out.println((System.getProperty("os.name", "linux") +
- ofNullable(System.getProperty("sun.arch.data.model"))
- .orElseGet(() -> System.getProperty("os.arch", "64").replace("amd", "")))
- .toLowerCase(ROOT));
- }
}
diff --git a/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/NativeImageMojo.java b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/NativeImageMojo.java
index 18bd42f..30dc0ee 100644
--- a/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/NativeImageMojo.java
+++ b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/NativeImageMojo.java
@@ -38,6 +38,7 @@
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -63,7 +64,6 @@
import org.apache.geronimo.arthur.spi.model.ResourceBundleModel;
import org.apache.geronimo.arthur.spi.model.ResourceModel;
import org.apache.maven.artifact.Artifact;
-import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.handler.DefaultArtifactHandler;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugins.annotations.Component;
@@ -440,6 +440,7 @@
.jsonSerializer(jsonb::toJson)
.annotatedClassFinder(finder::findAnnotatedClasses)
.annotatedMethodFinder(finder::findAnnotatedMethods)
+ .extensionProperties(getExtensionProperties())
.implementationFinder(p -> {
if (finderLinked.compareAndSet(false, true)) {
finder.enableFindImplementations().enableFindSubclasses();
@@ -489,6 +490,12 @@
}
}
+ private Map<String, String> getExtensionProperties() {
+ final Map<String, String> props = extensionProperties == null ? new HashMap<>() : new HashMap<>(extensionProperties);
+ props.putIfAbsent("classes", project.getBuild().getOutputDirectory());
+ return props;
+ }
+
private Predicate<Artifact> createScanningFilter() {
if (scanningExcludedArtifacts != null && scanningExcludedArtifacts.contains("*")) {
return a -> false;
diff --git a/documentation/src/content/knights.adoc b/documentation/src/content/knights.adoc
index f9a09ef..e276d79 100644
--- a/documentation/src/content/knights.adoc
+++ b/documentation/src/content/knights.adoc
@@ -22,6 +22,8 @@
== Available knights
- link:jsch-knight.html[JSch]: it contains some end user API integrated with built-in extensions to simplify application graal-ification,
+- link:winegrower-knight.html[Winegrower]: Apache winegrower (Cloud OSGi runtime) support.
+- link:openwebbeans-knight.html[OpenWebBeans]: Apache OpenWebBeans (CDI SE runtime) support.
== Configure a Knight in Arthur Maven plugin
diff --git a/documentation/src/content/openwebbeans-knight.adoc b/documentation/src/content/openwebbeans-knight.adoc
new file mode 100644
index 0000000..468a677
--- /dev/null
+++ b/documentation/src/content/openwebbeans-knight.adoc
@@ -0,0 +1,121 @@
+////
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+////
+= Arthur OpenWebBeans Knights
+
+Arthur OpenWebBeans knight is responsible to support Apache OpenWebBeans.
+It preconfigures the most of the reflection and resource inclusion.
+
+It assumes you use `openwebbeans-se` in your project.
+Here is a sample dependencies block for a simple SCR application:
+
+[source,xml]
+----
+<dependencies>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-jcdi_2.0_spec</artifactId>
+ <version>1.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-el_2.2_spec</artifactId>
+ <version>1.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-annotation_1.3_spec</artifactId>
+ <version>1.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-atinject_1.0_spec</artifactId>
+ <version>1.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-interceptor_1.2_spec</artifactId>
+ <version>1.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.openwebbeans</groupId>
+ <artifactId>openwebbeans-se</artifactId>
+ <version>2.0.21</version>
+ </dependency>
+</dependencies>
+----
+
+IMPORTANT: this only works out of the box with OpenWebBeans 2.0.21 or later. You'll need to tune the scanner service yourself for older versions.
+
+== Coordinates
+
+[source,xml]
+----
+<dependency>
+ <groupId>org.apache.geronimo.arthur.knights</groupId>
+ <artifactId>openwebbeans-knight</artifactId>
+ <version>${arthur.version}</version>
+</dependency>
+----
+
+== Usage
+
+To make a CDI application compatible with `native-image` there are a few necessary steps to do:
+
+. Generate all CDI proxies (normal scope beans, intercepted/decorated beans)
+. Include the proxies in the final image
+. Include OpenWebBeans log resource bundle
+. Register bean classes for reflection
+
+The `openwebbeans-knight` handles most of that even if you can need to tune a few details in the Arthur configuration.
+
+
+Finally, you must register the openwebbeans knight in `arthur-maven-plugin`:
+
+
+[source,xml]
+----
+<plugin>
+ <groupId>org.apache.geronimo.arthur</groupId>
+ <artifactId>arthur-maven-plugin</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <configuration>
+ <main>org.company.osgi.ScrMain</main>
+ <graalExtensions>
+ <graalExtension>openwebbeans</graalExtension>
+ </graalExtensions>
+ </configuration>
+</plugin>
+----
+
+== Configuration
+
+This knight has several configuration options:
+
+[opts="header",role="table table-bordered",cols="2,1,3"]
+|===
+|Name|Type|Description
+a|`extension.openwebbeans.classes.filter.[includes\|excludes]`|String|Comma separated values for scanning filter (packages).
+a|`extension.openwebbeans.classes.includeAsResources`|boolean|Should classes be included as resources (.class) too.
+a|`extension.openwebbeans.container.se.disableDiscovery`|boolean|Should CDI SE container auto discover the beans or not.
+a|`extension.openwebbeans.container.se.properties`|Properties|CDI SE properties.
+a|`extension.openwebbeans.container.se.services`|Properties|OpenWebBeans services as properties (SPI=IMPL).
+a|`extension.openwebbeans.container.se.classes`|String list|Bean classes to register.
+|===
+
+---
+
+Previous: link:knights.html[Knights]
diff --git a/integration-test/pom.xml b/integration-test/pom.xml
index f59c179..c0b443d 100644
--- a/integration-test/pom.xml
+++ b/integration-test/pom.xml
@@ -60,7 +60,7 @@
<scope>test</scope>
</dependency>
- <!-- just for the reactor -->
+ <!-- just for the reactor ordering -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>arthur-maven-plugin</artifactId>
@@ -85,6 +85,12 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}.knights</groupId>
+ <artifactId>openwebbeans-knight</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/integration-test/src/test/java/org/apache/geronimo/arthur/integrationtests/MavenTest.java b/integration-test/src/test/java/org/apache/geronimo/arthur/integrationtests/MavenTest.java
index 65cab08..8b7c780 100644
--- a/integration-test/src/test/java/org/apache/geronimo/arthur/integrationtests/MavenTest.java
+++ b/integration-test/src/test/java/org/apache/geronimo/arthur/integrationtests/MavenTest.java
@@ -46,6 +46,9 @@
@Spec(expectedOutput = "Cui-yère")
void cuilliere() {}
+ @Test
+ @Spec(expectedOutput = "counter=1, from proxy=from proxy")
+ void owb() {}
@Test
@Spec(expectedOutput = "Starting org.apache.geronimo.arthur.integrationtests.Application")
diff --git a/integration-test/src/test/resources/integration-tests/owb/pom.xml b/integration-test/src/test/resources/integration-tests/owb/pom.xml
new file mode 100644
index 0000000..ff66334
--- /dev/null
+++ b/integration-test/src/test/resources/integration-tests/owb/pom.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="
+ http://maven.apache.org/POM/4.0.0
+ http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.apache</groupId>
+ <artifactId>apache</artifactId>
+ <version>21</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.apache.geronimo.arthur.integrationtests</groupId>
+ <artifactId>owb</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <name>Arthur :: Integration Tests :: OWB</name>
+
+ <!-- not important to not reuse build versions, should be stable -->
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-jcdi_2.0_spec</artifactId>
+ <version>1.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-el_2.2_spec</artifactId>
+ <version>1.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-annotation_1.3_spec</artifactId>
+ <version>1.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-atinject_1.0_spec</artifactId>
+ <version>1.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-interceptor_1.2_spec</artifactId>
+ <version>1.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.openwebbeans</groupId>
+ <artifactId>openwebbeans-se</artifactId>
+ <version>2.0.21-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>${maven-compiler-plugin.version}</version>
+ <configuration>
+ <source>${maven.compiler.source}</source>
+ <target>${maven.compiler.target}</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.geronimo.arthur</groupId>
+ <artifactId>arthur-maven-plugin</artifactId>
+ <version>${project.version}</version>
+ <configuration>
+ <main>org.apache.geronimo.arthur.integrationtests.OWB</main>
+ <graalExtensions>
+ <graalExtension>openwebbeans</graalExtension>
+ </graalExtensions>
+ <graalCacheGav>org.apache.geronimo.arthur.cache:graal-integrationtests</graalCacheGav>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/integration-test/src/test/resources/integration-tests/owb/src/main/java/org/apache/geronimo/arthur/integrationtests/OWB.java b/integration-test/src/test/resources/integration-tests/owb/src/main/java/org/apache/geronimo/arthur/integrationtests/OWB.java
new file mode 100644
index 0000000..4ac5456
--- /dev/null
+++ b/integration-test/src/test/resources/integration-tests/owb/src/main/java/org/apache/geronimo/arthur/integrationtests/OWB.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geronimo.arthur.integrationtests;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.Initialized;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.se.SeContainer;
+import javax.enterprise.inject.se.SeContainerInitializer;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public final class OWB {
+ private OWB() {
+ // noop
+ }
+
+ public static void main(final String[] args) throws IOException {
+ Logger.getLogger("org.apache").setLevel(Level.WARNING);
+ try (final SeContainer container = SeContainerInitializer.newInstance().initialize()) {
+ // starter is launched automatically
+ final int counter = container.select(Starter.class).get().getCounter();
+ if (counter != 1) {
+ throw new IllegalStateException("Starter didn't start: " + counter);
+ }
+
+ final Proxied proxied = container.select(Proxied.class).get();
+ final String proxyValue = proxied.getAnything();
+ if (!"from proxy".equals(proxyValue)) {
+ throw new IllegalStateException(proxied + ": " + proxyValue);
+ }
+ System.out.println("counter=" + counter + ", from proxy=" + proxyValue);
+ }
+ }
+
+ private static void setIfMissing(final String key, final String value) {
+ System.setProperty(key, System.getProperty(key, value));
+ }
+
+ @ApplicationScoped
+ public static class Starter {
+ private int counter = 0;
+
+ public int getCounter() {
+ return counter;
+ }
+
+ public void onStart(@Observes @Initialized(ApplicationScoped.class) final Object start,
+ final Proxied proxied) {
+ counter++;
+ Logger.getLogger(getClass().getName() + // who
+ " started: proxy_class=" + proxied.getClass().getName() + ", " + // uses proxy class
+ proxied.getAnything()); // proxy works
+ }
+ }
+
+ @ApplicationScoped
+ public static class Proxied {
+ public String getAnything() {
+ return "from proxy";
+ }
+ }
+}
diff --git a/knights/openwebbeans-knight/pom.xml b/knights/openwebbeans-knight/pom.xml
index c38b978..6c3482c 100644
--- a/knights/openwebbeans-knight/pom.xml
+++ b/knights/openwebbeans-knight/pom.xml
@@ -69,7 +69,7 @@
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-se</artifactId>
- <version>2.0.20</version>
+ <version>2.0.21-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.graalvm.nativeimage</groupId>
diff --git a/knights/openwebbeans-knight/src/main/java/org/apache/geronimo/arthur/knight/openwebbeans/OpenWebBeansExtension.java b/knights/openwebbeans-knight/src/main/java/org/apache/geronimo/arthur/knight/openwebbeans/OpenWebBeansExtension.java
index 61ef51e..68484bb 100644
--- a/knights/openwebbeans-knight/src/main/java/org/apache/geronimo/arthur/knight/openwebbeans/OpenWebBeansExtension.java
+++ b/knights/openwebbeans-knight/src/main/java/org/apache/geronimo/arthur/knight/openwebbeans/OpenWebBeansExtension.java
@@ -16,11 +16,357 @@
*/
package org.apache.geronimo.arthur.knight.openwebbeans;
+import lombok.extern.slf4j.Slf4j;
import org.apache.geronimo.arthur.spi.ArthurExtension;
+import org.apache.geronimo.arthur.spi.model.ClassReflectionModel;
+import org.apache.geronimo.arthur.spi.model.ResourceModel;
+import org.apache.openwebbeans.se.CDISeScannerService;
+import org.apache.openwebbeans.se.PreScannedCDISeScannerService;
+import org.apache.webbeans.component.InjectionTargetBean;
+import org.apache.webbeans.component.ManagedBean;
+import org.apache.webbeans.config.OpenWebBeansConfiguration;
+import org.apache.webbeans.config.WebBeansContext;
+import org.apache.webbeans.container.BeanManagerImpl;
+import org.apache.webbeans.conversation.ConversationImpl;
+import org.apache.webbeans.corespi.DefaultSingletonService;
+import org.apache.webbeans.corespi.se.DefaultScannerService;
+import org.apache.webbeans.corespi.se.SimpleApplicationBoundaryService;
+import org.apache.webbeans.corespi.se.StandaloneContextsService;
+import org.apache.webbeans.inject.impl.InjectionPointImpl;
+import org.apache.webbeans.intercept.ApplicationScopedBeanInterceptorHandler;
+import org.apache.webbeans.intercept.NormalScopedBeanInterceptorHandler;
+import org.apache.webbeans.intercept.RequestScopedBeanInterceptorHandler;
+import org.apache.webbeans.intercept.SessionScopedBeanInterceptorHandler;
+import org.apache.webbeans.lifecycle.StandaloneLifeCycle;
+import org.apache.webbeans.logger.WebBeansLoggerFacade;
+import org.apache.webbeans.service.ClassLoaderProxyService;
+import org.apache.webbeans.service.DefaultLoaderService;
+import org.apache.webbeans.spi.ApplicationBoundaryService;
+import org.apache.webbeans.spi.BeanArchiveService;
+import org.apache.webbeans.spi.ContainerLifecycle;
+import org.apache.webbeans.spi.ContextsService;
+import org.apache.webbeans.spi.DefiningClassService;
+import org.apache.webbeans.spi.InjectionPointService;
+import org.apache.webbeans.spi.JNDIService;
+import org.apache.webbeans.spi.LoaderService;
+import org.apache.webbeans.spi.ResourceInjectionService;
+import org.apache.webbeans.spi.ScannerService;
+import org.apache.webbeans.spi.SecurityService;
+import org.apache.xbean.finder.filter.Filter;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.ConversationScoped;
+import javax.enterprise.context.Dependent;
+import javax.enterprise.context.Destroyed;
+import javax.enterprise.context.Initialized;
+import javax.enterprise.context.NormalScope;
+import javax.enterprise.context.RequestScoped;
+import javax.enterprise.event.Observes;
+import javax.enterprise.event.ObservesAsync;
+import javax.enterprise.event.Reception;
+import javax.enterprise.event.TransactionPhase;
+import javax.enterprise.inject.Default;
+import javax.enterprise.inject.se.SeContainer;
+import javax.enterprise.inject.se.SeContainerInitializer;
+import javax.enterprise.inject.spi.Bean;
+import javax.inject.Qualifier;
+import javax.interceptor.InterceptorBinding;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.lang.reflect.Field;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.Optional.ofNullable;
+import static java.util.stream.Collectors.joining;
+
+@Slf4j
public class OpenWebBeansExtension implements ArthurExtension {
@Override
public void execute(final Context context) {
+ final Properties original = new Properties();
+ original.putAll(System.getProperties());
+ try (final SeContainer container = configureInitializer(context, SeContainerInitializer.newInstance()).initialize()) {
+ final WebBeansContext webBeansContext = WebBeansContext.currentInstance();
+ final BeanManagerImpl beanManager = webBeansContext.getBeanManagerImpl();
+ final Set<Bean<?>> beans = beanManager.getBeans();
+ // 1. capture all proxies
+ dumpProxies(context, webBeansContext, beans);
+
+ // 2. register all classes which will require reflection + proxies
+ final String beanClassesList = registerBeansForReflection(context, beans);
+ getProxies(webBeansContext).keySet().forEach(name -> {
+ final ClassReflectionModel model = new ClassReflectionModel();
+ model.setName(name);
+ model.setAllDeclaredConstructors(true);
+ model.setAllDeclaredFields(true);
+ model.setAllDeclaredMethods(true);
+ context.register(model);
+ });
+
+ // 3. dump owb properties for runtime
+ final Properties properties = initProperties(context, webBeansContext.getOpenWebBeansConfiguration(), beanClassesList);
+
+ // 4. register CDI/OWB API which require some reflection
+ // 4.1 SPI (interface)
+ Stream.of(
+ ScannerService.class, LoaderService.class, BeanArchiveService.class, SecurityService.class,
+ ContainerLifecycle.class, JNDIService.class, ApplicationBoundaryService.class, ContextsService.class,
+ InjectionPointService.class, ResourceInjectionService.class, DefiningClassService.class,
+ Filter.class)
+ .forEach(clazz -> {
+ final ClassReflectionModel model = new ClassReflectionModel();
+ model.setName(clazz.getName());
+ model.setAllPublicMethods(true);
+ context.register(model);
+ });
+ // 4.2 classes which must be instantiable
+ Stream.concat(Stream.of(
+ ClassLoaderProxyService.LoadFirst.class, StandaloneLifeCycle.class, StandaloneContextsService.class,
+ DefaultLoaderService.class, InjectionPointImpl.class, ConversationImpl.class, SimpleApplicationBoundaryService.class,
+ ApplicationScopedBeanInterceptorHandler.class, RequestScopedBeanInterceptorHandler.class,
+ SessionScopedBeanInterceptorHandler.class, NormalScopedBeanInterceptorHandler.class,
+ CDISeScannerService.class, PreScannedCDISeScannerService.class, DefaultScannerService.class),
+ findServices(properties))
+ .distinct()
+ .forEach(clazz -> {
+ final ClassReflectionModel model = new ClassReflectionModel();
+ model.setName(clazz.getName());
+ model.setAllDeclaredConstructors(true);
+ context.register(model);
+ });
+ // 4.3 annotations
+ Stream.concat(Stream.concat(Stream.of(
+ Initialized.class, Destroyed.class, NormalScope.class, ApplicationScoped.class, Default.class,
+ Dependent.class, ConversationScoped.class, RequestScoped.class, Observes.class, ObservesAsync.class,
+ Qualifier.class, InterceptorBinding.class),
+ beanManager.getAdditionalQualifiers().stream()),
+ Stream.concat(
+ context.findAnnotatedClasses(Qualifier.class).stream(),
+ context.findAnnotatedClasses(NormalScope.class).stream()))
+ .distinct()
+ .forEach(clazz -> {
+ final ClassReflectionModel model = new ClassReflectionModel();
+ model.setName(clazz.getName());
+ model.setAllDeclaredMethods(true);
+ context.register(model);
+ });
+
+ // enforce some build time init for annotations and some specific classes
+ context.initializeAtBuildTime(
+ Reception.class.getName(),
+ TransactionPhase.class.getName(),
+ DefaultSingletonService.class.getName(),
+ WebBeansLoggerFacade.class.getName());
+
+ // we add the resource bundle in the feature not here
+ } finally {
+ System.setProperties(original);
+ }
+ }
+
+ private Stream<? extends Class<?>> findServices(final Properties properties) {
+ final ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ return properties.stringPropertyNames().stream()
+ .filter(it -> it.startsWith("org.apache.webbeans.spi.") || it.equals(Filter.class.getName()))
+ .map(properties::getProperty)
+ .map(it -> {
+ try {
+ return loader.loadClass(it);
+ } catch (final ClassNotFoundException e) {
+ if (it.contains(".")) {
+ log.warn(e.getMessage(), e);
+ } // else can be "false" so just ignore
+ return null;
+ }
+ })
+ .filter(Objects::nonNull);
+ }
+
+ private String registerBeansForReflection(final Context context, final Set<Bean<?>> beans) {
+ final Predicate<String> classFilter = context.createIncludesExcludes("extension.openwebbeans.classes.filter.", PredicateType.STARTS_WITH);
+ final boolean includeClassResources = Boolean.parseBoolean(context.getProperty("extension.openwebbeans.classes.includeAsResources"));
+ return beans.stream()
+ .filter(ManagedBean.class::isInstance)
+ .map(Bean::getBeanClass)
+ .flatMap(this::hierarchy)
+ .distinct()
+ .map(Class::getName)
+ .filter(classFilter)
+ .sorted()
+ .peek(clazz -> {
+ final ClassReflectionModel model = new ClassReflectionModel();
+ model.setName(clazz);
+ model.setAllDeclaredConstructors(true);
+ model.setAllDeclaredMethods(true);
+ model.setAllDeclaredFields(true);
+ context.register(model);
+
+ if (includeClassResources) {
+ final ResourceModel resourceModel = new ResourceModel();
+ resourceModel.setPattern(Pattern.quote(clazz.replace('.', '/') + ".class"));
+ context.register(resourceModel);
+ }
+ })
+ .collect(joining(","));
+ }
+
+ private void dumpProxies(final Context context, final WebBeansContext webBeansContext, final Set<Bean<?>> beans) {
+ // interceptors/decorators
+ beans.stream()
+ .filter(InjectionTargetBean.class::isInstance)
+ .map(InjectionTargetBean.class::cast)
+ .forEach(InjectionTargetBean::defineInterceptorsIfNeeded);
+ // normal scope
+ beans.stream()
+ .filter(it -> webBeansContext.getBeanManagerImpl().isNormalScope(it.getScope()))
+ .forEach(webBeansContext.getNormalScopeProxyFactory()::createNormalScopeProxy);
+
+ final Map<String, byte[]> proxies = getProxies(webBeansContext);
+ log.debug("Proxies: {}", proxies.keySet());
+ if (proxies.isEmpty()) {
+ log.info("No proxy found for this application");
+ } else {
+ proxies.forEach((className, bytes) -> {
+ context.registerGeneratedClass(className, bytes);
+ log.info("Registered proxy '{}'", className);
+ });
+ }
+ }
+
+ private Map<String, byte[]> getProxies(final WebBeansContext webBeansContext) {
+ return ClassLoaderProxyService.Spy.class.cast(webBeansContext.getService(DefiningClassService.class)).getProxies();
+ }
+
+ private Stream<Class<?>> hierarchy(final Class<?> it) {
+ return it == null || it == Object.class ?
+ Stream.empty() :
+ Stream.concat(Stream.of(it), hierarchy(it.getSuperclass()));
+ }
+
+ private Properties initProperties(final Context context, final OpenWebBeansConfiguration configuration,
+ final String beanClassesList) {
+ try {
+ final Field field = OpenWebBeansConfiguration.class.getDeclaredField("configProperties");
+ field.setAccessible(true);
+
+ final Properties properties = Properties.class.cast(field.get(configuration));
+ enrichProperties(properties, true);
+ if (!Boolean.parseBoolean(context.getProperty("extension.openwebbeans.services.ignoreScannerService"))) {
+ properties.put("org.apache.webbeans.spi.ScannerService", "org.apache.openwebbeans.se.PreScannedCDISeScannerService");
+ }
+ properties.putIfAbsent("org.apache.openwebbeans.se.PreScannedCDISeScannerService.classes", beanClassesList);
+
+ properties.remove("config.ordinal"); // no more needed since it will be unique
+ final StringWriter writer = new StringWriter();
+ try (final StringWriter w = writer) {
+ properties.store(w, "Generated by Geronimo Arthur");
+ }
+
+ final Path workDir = Paths.get(requireNonNull(context.getProperty("workingDirectory"), "workingDirectory property"));
+ context.addNativeImageOption("-H:OpenWebBeansProperties=" +
+ dump(workDir, "openwebbeans.properties", writer.toString().replaceAll("(?m)^#.*", "")));
+ return properties;
+ } catch (final Exception e) {
+ throw new IllegalStateException("Incompatible OWB version", e);
+ }
+ }
+
+ private void enrichProperties(final Properties properties, final boolean runtime) {
+ properties.setProperty("config.ordinal", "10000");
+
+ properties.setProperty("org.apache.webbeans.proxy.useStaticNames", "true");
+ properties.setProperty("org.apache.webbeans.proxy.staticNames.useXxHash64", "true");
+
+ properties.setProperty("org.apache.webbeans.spi.DefiningClassService", runtime ?
+ "org.apache.webbeans.service.ClassLoaderProxyService$LoadFirst" :
+ "org.apache.webbeans.service.ClassLoaderProxyService$Spy");
+ properties.setProperty("org.apache.webbeans.spi.ApplicationBoundaryService",
+ "org.apache.webbeans.corespi.se.SimpleApplicationBoundaryService");
+ }
+
+ private SeContainerInitializer configureInitializer(final Context context, final SeContainerInitializer initializer) {
+ final Properties config = new Properties();
+ enrichProperties(config, false); // before starting ensure we use a deterministic proxy generation config
+ config.stringPropertyNames().forEach(k -> initializer.addProperty(k, config.getProperty(k)));
+
+ if (Boolean.getBoolean(context.getProperty("extension.openwebbeans.container.se.disableDiscovery"))) {
+ initializer.disableDiscovery();
+ }
+ final ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ initializer.setClassLoader(loader);
+ ofNullable(context.getProperty("extension.openwebbeans.container.se.properties"))
+ .ifPresent(props -> {
+ final Properties properties = readProps(props);
+ properties.stringPropertyNames().forEach(k -> initializer.addProperty(k, properties.getProperty(k)));
+ });
+ ofNullable(context.getProperty("extension.openwebbeans.container.se.services"))
+ .ifPresent(props -> {
+ final Properties properties = readProps(props);
+ properties.stringPropertyNames().forEach(k -> {
+ try {
+ initializer.addProperty(k, loader.loadClass(properties.getProperty(k).trim()));
+ } catch (final ClassNotFoundException e) {
+ throw new IllegalArgumentException(e);
+ }
+ });
+ });
+ ofNullable(context.getProperty("extension.openwebbeans.container.se.classes"))
+ .ifPresent(classes -> initializer.addBeanClasses(Stream.of(classes.split(","))
+ .map(String::trim)
+ .filter(it -> !it.isEmpty())
+ .map(it -> {
+ try {
+ return loader.loadClass(it);
+ } catch (final ClassNotFoundException e) {
+ throw new IllegalArgumentException(e);
+ }
+ })
+ .toArray(Class<?>[]::new)));
+
+ return initializer;
+ }
+
+ private Properties readProps(final String props) {
+ final Properties properties = new Properties();
+ try (final StringReader reader = new StringReader(props)) {
+ properties.load(reader);
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ }
+ return properties;
+ }
+
+ private String dump(final Path workDir, final String name, final String value) {
+ if (!java.nio.file.Files.isDirectory(workDir)) {
+ try {
+ java.nio.file.Files.createDirectories(workDir);
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ final Path out = workDir.resolve(name);
+ try {
+ Files.write(
+ out, value.getBytes(StandardCharsets.UTF_8),
+ StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ log.info("Created '{}'", out);
+ return out.toAbsolutePath().toString();
}
}
diff --git a/knights/openwebbeans-knight/src/main/java/org/apache/geronimo/arthur/knight/openwebbeans/feature/OpenWebBeansFeature.java b/knights/openwebbeans-knight/src/main/java/org/apache/geronimo/arthur/knight/openwebbeans/feature/OpenWebBeansFeature.java
new file mode 100644
index 0000000..d15a1ef
--- /dev/null
+++ b/knights/openwebbeans-knight/src/main/java/org/apache/geronimo/arthur/knight/openwebbeans/feature/OpenWebBeansFeature.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geronimo.arthur.knight.openwebbeans.feature;
+
+import com.oracle.svm.core.annotate.AutomaticFeature;
+import com.oracle.svm.core.jdk.LocalizationFeature;
+import com.oracle.svm.core.jdk.Resources;
+import com.oracle.svm.core.option.HostedOptionKey;
+import org.graalvm.compiler.options.Option;
+import org.graalvm.compiler.options.OptionDescriptor;
+import org.graalvm.compiler.options.OptionDescriptors;
+import org.graalvm.compiler.options.OptionType;
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.hosted.Feature;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Iterator;
+import java.util.stream.Stream;
+
+@AutomaticFeature
+public class OpenWebBeansFeature implements Feature {
+ public static final class Options {
+ @Option(help = "OpenWebBeans properties.", type = OptionType.User)
+ static final HostedOptionKey<String> OpenWebBeansProperties = new HostedOptionKey<>(null);
+ }
+
+ // org.graalvm.compiler.options.processor is not on central
+ public static class OpenWebBeansOptions implements OptionDescriptors {
+ @Override
+ public OptionDescriptor get(final String value) {
+ switch (value) {
+ case "OpenWebBeansProperties":
+ return OptionDescriptor.create(
+ value, OptionType.User, String.class,
+ "OpenWebBeans properties.",
+ Options.class, value,
+ Options.OpenWebBeansProperties);
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public Iterator<OptionDescriptor> iterator() {
+ return Stream.of("OpenWebBeansProperties").map(this::get).iterator();
+ }
+ }
+
+ @Override
+ public void beforeAnalysis(final BeforeAnalysisAccess access) {
+ if (Options.OpenWebBeansProperties.hasBeenSet()) {
+ register(Options.OpenWebBeansProperties.getValue(), "META-INF/openwebbeans/openwebbeans.properties");
+ }
+ ImageSingletons.lookup(LocalizationFeature.class).addBundleToCache("openwebbeans/Messages");
+ }
+
+ private void register(final String path, final String resource) {
+ try (final InputStream stream = Files.newInputStream(Paths.get(path))) {
+ Resources.registerResource(resource, stream);
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git a/knights/openwebbeans-knight/src/main/java/org/apache/geronimo/arthur/knight/openwebbeans/replacement/AbstractMetaDataDiscovery.java b/knights/openwebbeans-knight/src/main/java/org/apache/geronimo/arthur/knight/openwebbeans/replacement/AbstractMetaDataDiscovery.java
new file mode 100644
index 0000000..c4a08fd
--- /dev/null
+++ b/knights/openwebbeans-knight/src/main/java/org/apache/geronimo/arthur/knight/openwebbeans/replacement/AbstractMetaDataDiscovery.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geronimo.arthur.knight.openwebbeans.replacement;
+
+import com.oracle.svm.core.annotate.Substitute;
+import com.oracle.svm.core.annotate.TargetClass;
+
+@TargetClass(org.apache.webbeans.corespi.scanner.AbstractMetaDataDiscovery.class)
+public final class AbstractMetaDataDiscovery {
+ @Substitute
+ protected void registerBeanArchives(final ClassLoader loader) {
+ // no-op: we don't discover we just take the generated/configured scanning
+ }
+}
diff --git a/knights/openwebbeans-knight/src/main/java/org/apache/geronimo/arthur/knight/openwebbeans/replacement/OWBInitializer.java b/knights/openwebbeans-knight/src/main/java/org/apache/geronimo/arthur/knight/openwebbeans/replacement/OWBInitializer.java
new file mode 100644
index 0000000..159652c
--- /dev/null
+++ b/knights/openwebbeans-knight/src/main/java/org/apache/geronimo/arthur/knight/openwebbeans/replacement/OWBInitializer.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geronimo.arthur.knight.openwebbeans.replacement;
+
+import com.oracle.svm.core.annotate.Substitute;
+import com.oracle.svm.core.annotate.TargetClass;
+import org.apache.openwebbeans.se.PreScannedCDISeScannerService;
+import org.apache.webbeans.spi.ScannerService;
+
+@TargetClass(org.apache.openwebbeans.se.OWBInitializer.class)
+public final class OWBInitializer {
+ @Substitute
+ protected ScannerService getScannerService() {
+ final PreScannedCDISeScannerService scannerService = new PreScannedCDISeScannerService();
+ scannerService.loader(Thread.currentThread().getContextClassLoader());
+ return scannerService;
+ }
+}
diff --git a/knights/openwebbeans-knight/src/main/resources/META-INF/services/org.apache.geronimo.arthur.spi.ArthurExtension b/knights/openwebbeans-knight/src/main/resources/META-INF/services/org.apache.geronimo.arthur.spi.ArthurExtension
new file mode 100644
index 0000000..24e264e
--- /dev/null
+++ b/knights/openwebbeans-knight/src/main/resources/META-INF/services/org.apache.geronimo.arthur.spi.ArthurExtension
@@ -0,0 +1 @@
+org.apache.geronimo.arthur.knight.openwebbeans.OpenWebBeansExtension
diff --git a/knights/openwebbeans-knight/src/main/resources/META-INF/services/org.graalvm.compiler.options.OptionDescriptors b/knights/openwebbeans-knight/src/main/resources/META-INF/services/org.graalvm.compiler.options.OptionDescriptors
new file mode 100644
index 0000000..71ed3d1
--- /dev/null
+++ b/knights/openwebbeans-knight/src/main/resources/META-INF/services/org.graalvm.compiler.options.OptionDescriptors
@@ -0,0 +1 @@
+org.apache.geronimo.arthur.knight.openwebbeans.feature.OpenWebBeansFeature$OpenWebBeansOptions