quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.69k stars 2.65k forks source link

Microprofile Config doesn't read config profiled properties when injected into servlet.Filter class in native mode #23807

Closed pbezpalko closed 2 years ago

pbezpalko commented 2 years ago

Describe the bug

It seems to quarkus-undertow (processing Filter classes) doesn't work properly with profiled Quarkus configuration under native image.

Injected org.eclipse.microprofile.config.Config instance in class implementing javax.servlet.Filter doesn't contain values defined in active profile. But this happens only in native mode. Under standard JVM everything works fine and all variables (core + profiled) are available.

Filter class:

@WebFilter(filterName = "BasicAuthFilter", urlPatterns = "/urlpattern/*")
public class BasicAuthFilter implements Filter {
...
 @javax.inject.Inject 
  org.eclipse.microprofile.config.Config config;

    @Override
    public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
            final FilterChain filterChain) throws IOException, ServletException {
          .....
            Optional<String> env = config.getOptionalValue("stage.environment");
          .....
    }

Configuration file:

quarkus:
  package:
    type: uber-jar
  log:
    level: INFO
'%NONE-CI':
  stage:
    environment: CI

Run under JVM: java -jar ... -Dquarkus.profile=NONE-CI....

Run under Native: ./application -Dquarkus.profile=NONE-CI

Expected behavior

For both, native and standard jvm, execution of config.getOptionalValue("stage.environment") returns the same value if the same profile will be used.

Actual behavior

config.getOptionalValue("stage.environment") works invalid when application is started under native mode and returns empty Optional object.

config.getOptionalValue("stage.environment") works properly when application is started under standard JVM mode and returns value "CI" - which is perfectly OK.

My working Workaround:

// request scope is also fine
@javax.enterprise.context.ApplicationScoped
public class Helper {

   // injection by field works as well
   private Config config;

    @Inject
    public void setConfig(final Config config) {
        this.config = config;
    }

   public Optional<String> getConfigValue(String key) {
         config.getOptionalValue(key);
   }
}

public .... implements javax.servlet.Filter {

   @Inject 
   Helper  helper;

   // then somewhere  in code....
     helper.getConfigValue("stage.environment");
  // returns always valid value - whether in JVM or native mode

}

How to Reproduce?

No response

Output of uname -a or ver

Microsoft Windows [Version 10.0.19042.1466] / Native run under Docker image based on registry.access.redhat.com/ubi8/ubi-minimal:8.4

Output of java -version

OpenJDK Runtime Environment Corretto-11.0.7.10.1 (build 11.0.7+10-LTS)

GraalVM version (if different from Java)

quarkus-mandrel:21.3-java11

Quarkus version or git rev

2.5.0.Final

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

Apache Maven 3.5.4 (1edded0938998edf8bf061f1ceb3cfdeccf443fe; 2018-06-17T20:33:14+02:00) Java version: 11.0.7, vendor: Amazon.com Inc., runtime: C:\Programs\jdk-11-amazoncorretto Default locale: en_US, platform encoding: Cp1252 OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

Additional information

Application compile & build:

mvn clean package 
   -Pnative 
   -Dquarkus.package.type=native 
   -Dquarkus.native.container-build=true 
   -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel:21.3-java11

I verified this also in servlet class (extends javax.servlet.http.HttpServlet). In this case all works properly and variables are read correctly both from directly injected Config instance and indirectly injected via Helper (application scoped) class.

quarkus-bot[bot] commented 2 years ago

/cc @Karm, @galderz, @zakkak

galderz commented 2 years ago

Hmmmm, I'm no expert in this area, but I wonder if quarkus.profile=NONE-CI should be passed at build time for this to work? I'm unsure how this would be done.

@gsmet @geoand This is not really about Mandrel, but about Quarkus resolution of configuration property names. When/where do those happen?

geoand commented 2 years ago

Hmmmm, I'm no expert in this area, but I wonder if quarkus.profile=NONE-CI should be passed at build time for this to work? I'm unsure how this would be done.

Shouldn't be necessary.

@gsmet @geoand This is not really about Mandrel, but about Quarkus resolution of configuration property names. When/where do those happen?

It's complicated, but this should work.

geoand commented 2 years ago

@pbezpalko any chance you could put together a sample application that exhibits the problematic behavior?

pbezpalko commented 2 years ago

Hi @geoand - yes, I will try to prepare the simplest application demonstrating all the problems found. I hope to be able to do this later this week.

pbezpalko commented 2 years ago

Hi @geoand - I prepare simple demo for this bug - can be found here: https://github.com/pbezpalko/quarkus-issues-23807

There is README.md with detailed information how to build and run. The bug is quite visible :)

geoand commented 2 years ago

Looks like one for @radcortez

radcortez commented 2 years ago

I'll have a look.

radcortez commented 2 years ago

This is a very interesting case. Let me see if I can explain what is happening properly:

The filters are initialized during the native image build, so the config instance that gets injected is also one that is created during static initialization, which uses the profile set during the native image build (if omitted is prod).

In the other case with the helper, the filter is also initialized during the native image build time and the injection point gets a lazy reference that is only created when required (when the app is called for the first time). In this case, the config instance you get is one that is created during runtime which uses the profile set by the user in the command line.

One way to fix this is to wrap your Config instance in Instance<Config> config.

But this raises some additional questions:

@geoand @mkouba any thoughts?

geoand commented 2 years ago

Logging a warning sounds like a good idea to me

mkouba commented 2 years ago

Well, it's a known issue but I have no idea how to detect these cases...

One way to fix this is to wrap your Config instance in Instance config.

Yes, a general workaround is to fetch the config lazily at runtime. You can do the same with @ConfigProperty Instance<TYPE> as well; in this case you can leverage the @io.quarkus.arc.WithCaching annotation to avoid property lookup for every Instance.get().