Repository: quarkusio/quarkus
Issue: ClassNotFoundException when scheduler calls static @Transactional PanacheEntity method

### Describe the bug

The following method cannot be called from a `@Scheduled` annotated method, if the entity class is imported from a lib and not part of the same source:
```java
@Entity
public class ExternalEntity extends PanacheEntity {
    ...
    @Transactional
    public static long doStuff() { ... }
}
```

### Expected behavior

This call should work without throwing an exception:
```java
@Scheduled(every = "1h")
public void run() {
  ExternalEntity.doStuff();
}
```

### Actual behavior

Class not found:
```
2023-04-28 15:31:41,012 ERROR [io.qua.sch.com.run.StatusEmitterInvoker] (vert.x-worker-thread-0) Error occurred while executing task for trigger IntervalTrigger [id=1_org.acme.Reproducer_ScheduledInvoker_run_72e66771a77415a7284d3ae42331659c186071de, interval=3600000]: java.util.concurrent.CompletionException: java.lang.NoClassDefFoundError: org/acme/entity/Entity_InterceptorInitializer
        at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:332)
        at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:347)
        at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:874)
        at java.base/java.util.concurrent.CompletableFuture.uniWhenCompleteStage(CompletableFuture.java:887)
        at java.base/java.util.concurrent.CompletableFuture.whenComplete(CompletableFuture.java:2325)
        at java.base/java.util.concurrent.CompletableFuture$MinimalStage.whenComplete(CompletableFuture.java:2902)
        at io.quarkus.scheduler.common.runtime.DefaultInvoker.invoke(DefaultInvoker.java:24)
        at io.quarkus.scheduler.common.runtime.StatusEmitterInvoker.invoke(StatusEmitterInvoker.java:35)
        at io.quarkus.scheduler.runtime.SimpleScheduler$ScheduledTask.doInvoke(SimpleScheduler.java:333)
        at io.quarkus.scheduler.runtime.SimpleScheduler$ScheduledTask$1.handle(SimpleScheduler.java:314)
        at io.quarkus.scheduler.runtime.SimpleScheduler$ScheduledTask$1.handle(SimpleScheduler.java:310)
        at io.vertx.core.impl.ContextBase.lambda$null$0(ContextBase.java:137)
        at io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:264)
        at io.vertx.core.impl.ContextBase.lambda$executeBlocking$1(ContextBase.java:135)
        at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1452)
        at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
        at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.NoClassDefFoundError: org/acme/entity/Entity_InterceptorInitializer
        at org.acme.entity.Entity.deleteByField(Entity.java)
        at org.acme.Reproducer.run(Reproducer.java:13)
        at org.acme.Reproducer_ClientProxy.run(Unknown Source)
        at org.acme.Reproducer_ScheduledInvoker_run_72e66771a77415a7284d3ae42331659c186071de.invokeBean(Unknown Source)
        ... 15 more
Caused by: java.lang.ClassNotFoundException: org.acme.entity.Entity_InterceptorInitializer
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
        at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:516)
        at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:466)
        ... 19 more
```

### How to Reproduce?

1. Download & unzip [reproducer.zip](https://github.com/quarkusio/quarkus/files/11353916/reproducer.zip)
2. `mvn -f entity/pom.xml install`
3. `mvn -f app/pom.xml quarkus:dev`

### Output of `uname -a` or `ver`

Microsoft Windows [Version 10.0.19045.2728]

### Output of `java -version`

openjdk version "17.0.6" 2023-01-17

### GraalVM version (if different from Java)

_No response_

### Quarkus version or git rev

2.16.6.Final & 3.0.1.Final

### Build tool (ie. output of `mvnw --version` or `gradlew --version`)

Maven 3.8.1

### Additional information

Tested with:
JDBC extension: `quarkus-jdbc-h2` & `quarkus-jdbc-mysql`
Panache extension: `quarkus-hibernate-orm-panache` (reactive untested)
Scheduler extension: `quarkus-scheduler`

Possible workarounds:
Move the entity class or the `@Transactional` annotation into the main project.

Changed files:
extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptedStaticMethodsRecorder.java
extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java
extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodBuildItem.java

diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodBuildItem.java
index 991405d1cfd..bc565c4bf15 100644
--- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodBuildItem.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodBuildItem.java
@@ -60,4 +60,12 @@ public String getHash() {
         return hash;
     }
 
+    /**
+     *
+     * @return the name of the generated forwarding method
+     */
+    public String getForwardingMethodName() {
+        return "_" + hash;
+    }
+
 }
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java
index 6e5f32ac07e..0e20e735c60 100644
--- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java
@@ -17,6 +17,7 @@
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.function.BiFunction;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 import jakarta.enterprise.context.spi.Contextual;
@@ -39,6 +40,7 @@
 import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
 import io.quarkus.arc.deployment.BeanContainerBuildItem;
 import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem;
+import io.quarkus.arc.deployment.CompletedApplicationClassPredicateBuildItem;
 import io.quarkus.arc.deployment.InterceptorResolverBuildItem;
 import io.quarkus.arc.deployment.TransformedAnnotationsBuildItem;
 import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
@@ -146,6 +148,7 @@ void collectInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex,
     void processInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex,
             BeanRegistrationPhaseBuildItem phase,
             List<InterceptedStaticMethodBuildItem> interceptedStaticMethods,
+            CompletedApplicationClassPredicateBuildItem applicationClassPredicate,
             BuildProducer<GeneratedClassBuildItem> generatedClasses,
             BuildProducer<BytecodeTransformerBuildItem> transformers,
             BuildProducer<ReflectiveMethodBuildItem> reflectiveMethods) {
@@ -154,7 +157,27 @@ void processInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex,
             return;
         }
 
-        ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, true);
+        // org.acme.Foo -> org.acme.Foo_InterceptorInitializer
+        Map<DotName, String> baseToGeneratedInitializer = new HashMap<>();
+        ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, new Predicate<String>() {
+
+            @Override
+            public boolean test(String name) {
+                // For example, for base org.acme.Foo we generate org.acme.Foo_InterceptorInitializer
+                // and possibly anonymous classes like org.acme.Foo_InterceptorInitializer$$function$$1
+                name = name.replace('/', '.');
+                DotName base = null;
+                for (Entry<DotName, String> e : baseToGeneratedInitializer.entrySet()) {
+                    if (e.getValue().equals(name) || name.startsWith(e.getValue())) {
+                        base = e.getKey();
+                    }
+                }
+                if (base == null) {
+                    throw new IllegalStateException("Unable to find the base class for the generated: " + name);
+                }
+                return applicationClassPredicate.test(base);
+            }
+        });
 
         // declaring class -> intercepted static methods
         Map<DotName, List<InterceptedStaticMethodBuildItem>> interceptedStaticMethodsMap = new HashMap<>();
@@ -172,14 +195,14 @@ void processInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex,
         // 1. registers all interceptor chains inside an "init_static_intercepted_methods" method
         // 2. adds static methods to invoke the interceptor chain and delegate to the copy of the original static method
         // declaring class -> initializer class
-        Map<DotName, String> initializers = new HashMap<>();
+
         String initAllMethodName = "init_static_intercepted_methods";
         for (Entry<DotName, List<InterceptedStaticMethodBuildItem>> entry : interceptedStaticMethodsMap.entrySet()) {
 
             String packageName = DotNames.internalPackageNameWithTrailingSlash(entry.getKey());
             String initializerName = packageName.replace("/", ".") + entry.getKey().withoutPackagePrefix()
                     + INITIALIZER_CLASS_SUFFIX;
-            initializers.put(entry.getKey(), initializerName);
+            baseToGeneratedInitializer.put(entry.getKey(), initializerName);
 
             ClassCreator initializer = ClassCreator.builder().classOutput(classOutput)
                     .className(initializerName).setFinal(true).build();
@@ -206,16 +229,17 @@ void processInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex,
         // For each intercepted static methods create a copy and modify the original method to delegate to the relevant initializer
         for (Entry<DotName, List<InterceptedStaticMethodBuildItem>> entry : interceptedStaticMethodsMap.entrySet()) {
             transformers.produce(new BytecodeTransformerBuildItem(entry.getKey().toString(),
-                    new InterceptedStaticMethodsEnhancer(initializers.get(entry.getKey()), entry.getValue())));
+                    new InterceptedStaticMethodsEnhancer(baseToGeneratedInitializer.get(entry.getKey()), entry.getValue())));
         }
 
-        // Generate a global initializer that calls all other initializers
-        ClassCreator globalInitializer = ClassCreator.builder().classOutput(classOutput)
+        // Generate a global initializer that calls all other initializers; this initializer must be loaded by the runtime ClassLoader
+        ClassCreator globalInitializer = ClassCreator.builder()
+                .classOutput(new GeneratedClassGizmoAdaptor(generatedClasses, true))
                 .className(InterceptedStaticMethodsRecorder.INITIALIZER_CLASS_NAME.replace('.', '/')).setFinal(true).build();
 
         MethodCreator staticInit = globalInitializer.getMethodCreator("<clinit>", void.class)
                 .setModifiers(ACC_STATIC);
-        for (String initializerClass : initializers.values()) {
+        for (String initializerClass : baseToGeneratedInitializer.values()) {
             staticInit.invokeStaticMethod(
                     MethodDescriptor.ofMethod(initializerClass, initAllMethodName, void.class));
         }
@@ -242,7 +266,8 @@ private void implementForward(ClassCreator initializer,
             paramTypes[i] = DescriptorUtils.typeToString(params.get(i));
         }
         MethodCreator forward = initializer
-                .getMethodCreator(interceptedStaticMethod.getHash(), DescriptorUtils.typeToString(method.returnType()),
+                .getMethodCreator(interceptedStaticMethod.getForwardingMethodName(),
+                        DescriptorUtils.typeToString(method.returnType()),
                         paramTypes)
                 .setModifiers(ACC_PUBLIC | ACC_FINAL | ACC_STATIC);
         ResultHandle argArray = forward.newArray(Object.class, params.size());
@@ -491,7 +516,7 @@ public void visitEnd() {
                 paramSlot += AsmUtil.getParameterSize(paramType);
             }
             superVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
-                    initializerClassName.replace('.', '/'), interceptedStaticMethod.getHash(),
+                    initializerClassName.replace('.', '/'), interceptedStaticMethod.getForwardingMethodName(),
                     descriptor.getDescriptor().toString(),
                     false);
             superVisitor.visitInsn(AsmUtil.getReturnInstruction(interceptedStaticMethod.getMethod().returnType()));
diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptedStaticMethodsRecorder.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptedStaticMethodsRecorder.java
index 2c62ba21a54..86a993c174a 100644
--- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptedStaticMethodsRecorder.java
+++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptedStaticMethodsRecorder.java
@@ -5,6 +5,7 @@
 @Recorder
 public class InterceptedStaticMethodsRecorder {
 
+    // This class is generated and calls all generated static interceptor initializers to register metadata in the InterceptedStaticMethods class
     public static final String INITIALIZER_CLASS_NAME = "io.quarkus.arc.runtime.InterceptedStaticMethodsInitializer";
 
     public void callInitializer() {