I am trying to build graalvm native image of my micronaut application. I see the werid issue that some of the properties from application.yaml are ignored, though when I run the app via
./gradlew run
all works fine.
Here is my yaml
micronaut:
application:
name: phonebook
caches:
phonebook:
charset: UTF-8
router:
static-resources:
swagger:
paths: classpath:META-INF/swagger
mapping: /swagger/**
swagger-ui:
paths: classpath:META-INF/swagger/views/swagger-ui
mapping: /doc/**
endpoints:
caches:
enabled: true
# sensitive: false
env:
enabled: true
# sensitive: false
fauna:
secret: '${FAUNA_KEY}'
endpoint: https://db.eu.fauna.com:443
Here is how I read the properties in the class
#Value("${fauna.secret}")
private String faunaKey;
#Value("${fauna.endpoint}")
private String faunaEndpoint;
What could cause an issue?
I know this question has been asked a while ago, but after having spent some hours figuring out the solution to a very similar problem, I thought I'd share the solution here.
I found that apparently the graal compiler throws any private fields away, thus throwing an error saying something like:
Caused by: java.lang.NoSuchFieldError: No field 'value1' found for type: com.example.ConferenceConfig
The solution that worked for me, was to annotate the classes in question with #ReflectiveAccess.
However other solutions also work; use constructor injection or configure src/main/graal/reflect.json
#Factory
public class ConferenceConfig {
private final String value1;
public ConferenceConfig(#Value("${my.value1}") final String value1) {
this.value1 = value1;
}
#Context
public ConferenceConfigBean confBean() {
return new ConferenceConfigBean(value1);
}
public record ConferenceConfigBean(String value) {
public String getValue() {
return value;
}
}
}
This also works for records:
#Singleton
public record ConferenceService(#Value("${my.value1}") String value1) {
private static final List<Conference> CONFERENCES = Arrays.asList(
new Conference("Greach"),
new Conference("GR8Conf EU"),
new Conference("Micronaut Summit"),
new Conference("Devoxx Belgium"),
new Conference("Oracle Code One"),
new Conference("CommitConf"),
new Conference("Codemotion Madrid")
);
public Conference randomConf() {
return CONFERENCES.get(new Random().nextInt(CONFERENCES.size()));
}
public String getV1() {
return value1;
}
}
Another
Related
I know this must be simple, and I've seen multiple similar questions, however my entire setup seems to be ok (as solutioned in the other posts), yet this problem persists.
Here's my setup
Environment
Spring Boot 2.6.3
Java 17
application.yml
platforms:
configs:
- platform: ABC
base-url: https://some-url-01.com/api
description:
logo:
- platform: DEF
base-url: https://some-url-02.com/api
description:
logo:
Config Properties
#Data
#ConstructorBinding
#ConfigurationProperties(prefix = "platforms")
public class PlatformProperties {
private final List<PlatformConfig> configs = new ArrayList<>();
#Data
public static class PlatformConfig {
private final Platform platform;
private final String baseUrl;
private final String description;
private final String logo;
}
}
Platform.java - a simple enum
public enum Platform {
ABC, DEF
}
Configuration
#Slf4j
#Configuration
#RequiredArgsConstructor
#EnableConfigurationProperties(PlatformProperties.class)
public class ClientConfig {
private final PlatformProperties platformProperties;
#PostConstruct
public void showProperties(){
platformProperties.getConfigs().forEach(System.out::println);
}
}
This entire setup seems perfectly fine (Ref: Spring Docs), however platformProperties.getConfigs() is always empty because there was no binding on platforms.configs as defined from the application.yml
I have a similar setup on a different project (springboot 2.5.7 / Java 8) where everything works exactly as expected.
What about this setup/configs is wrong???
Yah, I solved this a long time ago, just wanted to provide the answer, and it was quite simple too.
You see this line?
private final List<PlatformConfig> configs = new ArrayList<>();
That was the culprit.
Notice that the configs variable is final and was already assigned a new ArrayList<>() as it's value, hence it was immutable.
Solution was to remove the initial assignment so the line became;
private final List<PlatformConfig> configs;
The constructor binding went OK and the configs values were populated as expected.
For my Quarkus application I'm looking for a way to define a configuration map from within a custom ConfigProperties class. I tried the following:
import io.quarkus.arc.config.ConfigProperties;
import io.quarkus.runtime.annotations.ConfigItem;
#ConfigProperties(prefix = "my-properties")
public class MyPropertiesConfiguration {
#ConfigItem
public Map<String, FooConfiguration> foo;
// ...
}
import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;
#ConfigGroup
public class FooConfiguration {
#ConfigItem
public String myProperty;
}
Given those two classes and the following application.properties file...
my-properties.foo.anystring.my-property=bar
on startup the application fails with error message:
javax.enterprise.inject.spi.DeploymentException: No config value of type [java.util.Map] exists for: my-properties.foo
As far as I understand https://quarkus.io/guides/writing-extensions#configuration-maps the sample should work. What am I doing wrong? Could it happen that this functionality is just limited to Quarkus extensions only?
As written in this Quarkus github issue, this is currently not supported.
My dirty workaround was to use the ConfigProvider directly. Use with care.
public static Map<String, String> getMapFromConfig(String prefix) {
final Config config = ConfigProvider.getConfig();
final Iterable<String> propertyNames = config.getPropertyNames();
return StreamSupport.stream(propertyNames.spliterator(), false)
.filter(name -> name.startsWith(prefix) && !name.equalsIgnoreCase(prefix))
.collect(
Collectors.toMap(
propertyName -> cleanupPropertyName(propertyName.substring(prefix.length() + 1)),
propertyName -> config.getOptionalValue(propertyName, String.class).orElse("")));
}
/** Remove start and end double quotes */
public static String cleanupPropertyName(String name) {
if (name.startsWith("\"") && name.endsWith("\"")) {
return name.substring(1, name.length() - 1);
}
return name;
}
My config looks like this:
property-templates:
"my.key": value 1
"my.second.key": value 2
Declare the configuration like this
import io.quarkus.arc.config.ConfigProperties;
#ConfigProperties(prefix = "myapp")
public class AppSpecificConfig {
public String property;
}
The application.properties file will contain
myapp.property=foo
And then you can #Inject an instance of this class anywhere within your application.
For more details, see https://quarkus.io/guides/config#using-configproperties
I have problem with customizing API gateway domain, for my restful app deployed on AWS lambda. Customized domain, works this way, that depending on basePath it chooses different APIs which finally touches Lambda. For example:
api.mycustomdomain.com/view/ping -> goes to application view with path /view/ping
api.mycustomdomain.com/admin/ping -> goes to application admin with path /admin/ping
I am using this example as boilerplate: https://github.com/awslabs/aws-serverless-java-container/tree/master/samples/spring/pet-store
What I would like to achieve is handler which depending on Host header strips prefix from request path.
I have prepared following application.yml file:
server:
contextPath: "/view"
productionHost: "api.mycustomdomain.com"
The problem/question is. How can I now load those into my Lambda function? Here is my naive try:
public class LambdaHandler implements RequestHandler<AwsProxyRequest, AwsProxyResponse> {
SpringLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
boolean isinitialized = false;
#Value("${server.contextPath}")
private String prefix;
#Value("${server.productionHost}")
private String productionHost;
public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) {
if(awsProxyRequest.getHeaders().get("Host").equals(productionHost))
awsProxyRequest.setPath(awsProxyRequest.getPath().substring(prefix.length()));
if (!isinitialized) {
isinitialized = true;
try {
handler = SpringLambdaContainerHandler.getAwsProxyHandler(PingPongApp.class);
} catch (ContainerInitializationException e) {
e.printStackTrace();
return null;
}
}
return handler.proxy(awsProxyRequest, context);
}
}
Obviously this doesn't work, LambdaHandler is working out of Spring context.
Any ideas how can I deal with that?
It seems you can not load those properties. Follow either of the 2 options given below.
1> You can add following bean in your configuration and that way you can autowire strings and use the way you are already using
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
return new PropertySourcesPlaceholderConfigurer();
}
2>
public AwsProxyResponse..{
#Autowired
private Environment env;
..
public AwsProxyResponse handleRequest{
..
String contextPath = env.getRequiredProperty(“server.contextPath”));
...
}
}
I'm building a web service that needs to switch between two sets of properties depending on the request URL coming in. I'm not sure which is the best method of handling this.
I've got a Spring Boot app that has an yaml properties file. Inside the properties file the structure looks something like this;
optionA:
foo:
urls:
- a
- b
bar:
something: hello
optionB:
foo:
urls:
- x
- y
bar:
something: bye
Both optionA and optionB have pretty much all the same properties, just different values.
So a request comes in, I check the request and decide if I need optionA or optionB.
I've been trying to get #ConfigurationProperties to handle this but the properties are initialised on startup so it can't be dynamic. Another possibility is that I have two Configuration classes, one for each option but then my code gets full of checks to switch between the two classes and the classes are pretty much identical, not really nice either.
Any best practices or recommendations on how to best manage this would be appreciated, cheers!
If you have not too many options I would go this way: (Just made example with smaller config)
options.yml:
optionA:
name: optionA
optionB:
name: optionB
I created a Option class for extension:
public class Option {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And two Option classes where the #ConfigurationProperties are getting set: (For now these classes are empty but you have the opportunity to be more specific on each different option)
#Component
#ConfigurationProperties(prefix ="optionA", locations = "classpath:options.yml")
public class OptionA extends Option{
}
#Component
#ConfigurationProperties(prefix ="optionB", locations = "classpath:options.yml")
public class OptionB extends Option{
}
For the decision of the different options I created an interface:
public interface OptionService {
Option findOption(boolean businessLogic);
}
And in the implementation I inject both options and the implementation of the business logic: (in an easy way)
#Service
public class OptionServiceImpl implements OptionService {
private OptionA optionA;
private OptionB optionB;
#Override
public Option findOption(boolean businessLogic) {
if(businessLogic){
return getOptionA();
} else {
return getOptionB();
}
}
public OptionA getOptionA() {
return optionA;
}
#Autowired
public void setOptionA(OptionA optionA) {
this.optionA = optionA;
}
public OptionB getOptionB() {
return optionB;
}
#Autowired
public void setOptionB(OptionB optionB) {
this.optionB = optionB;
}
}
And at the end your controller just call the OptionServiceImpl class and deceide which option should be used:
#Controller
public class YourController {
private OptionService optionServiceImpl;
#RequestMapping("/")
public String getIndex(){
Option option = getOptionServiceImpl().findOption(true);
System.out.println(option.getName());
option = getOptionServiceImpl().findOption(false);
System.out.println(option.getName());
return "Hello World";
}
public OptionService getOptionServiceImpl() {
return optionServiceImpl;
}
#Autowired
public void setOptionServiceImpl(OptionService optionServiceImpl) {
this.optionServiceImpl = optionServiceImpl;
}
}
Output of System.out.println:
optionA
optionB
So your business logic to decide which option should be used is not an if - else construct. You are able to create the rules for the decission in the interface and its implementation. I think you are able to create more rules for more controllers.
Change your yml to:
options:
- name: optionA
foo:
urls:
- a
- b
bar:
something: hello
- name: optionB
foo:
urls:
- x
- y
bar:
something: bye
Add Config class:
#Data
#ConfigurationProperties
#Configuration
public class MyConfig {
private List<Option> options;
}
Use it:
#Component
public class UseConfig {
#Autowired
public UseConfig(final MyConfig config) {
System.out.println(config.getOptions());
}
}
Result:
[Option(name=optionA, foo=Foo(urls=[a, b]), bar=Bar(something=hello)), Option(name=optionB, foo=Foo(urls=[x, y]), bar=Bar(something=bye))]
You can define key value pairs in application.properties.
where key is web service name and value is option(list of properties)
Make use of #ConfigurationProperties
#ConfigurationProperties
class Configuration {
Map<String,Option> options;
// getters and setters
}
#Component
class ChooseServiceBasedConfiguration {
#Autowired
Configuration configuration;
public void serviceMethod(String key ){
//get appropriate properties of the web service
configuration.getOptions().get(key);
}
}
based on web service get the values required using the key .
So I want to change the validation messages used to validate a model through a DropWizard resource.
I'm using java bean validation annotations. For example here is one of the fields I want to validate:
#NotEmpty(message = "Password must not be empty.")
I can test this works as expected using a validator.
However when I use DropWizard to do the validation on the resource it adds some extra stuff to that message. What I see is this - password Password must not be empty. (was null) and I've found the code that does this here - https://github.com/dropwizard/dropwizard/blob/master/dropwizard-validation/src/main/java/io/dropwizard/validation/ConstraintViolations.java
Specifically this method -
public static <T> String format(ConstraintViolation<T> v) {
if (v.getConstraintDescriptor().getAnnotation() instanceof ValidationMethod) {
final ImmutableList<Path.Node> nodes = ImmutableList.copyOf(v.getPropertyPath());
final ImmutableList<Path.Node> usefulNodes = nodes.subList(0, nodes.size() - 1);
final String msg = v.getMessage().startsWith(".") ? "%s%s" : "%s %s";
return String.format(msg,
Joiner.on('.').join(usefulNodes),
v.getMessage()).trim();
} else {
return String.format("%s %s (was %s)",
v.getPropertyPath(),
v.getMessage(),
v.getInvalidValue());
}
}
Is there any way I can override this behaviour? I just want to display the message that I set in the annotation...
Here is a programmatic solution in dropwizard 0.8:
public void run(final MyConfiguration config, final Environment env) {
AbstractServerFactory sf = (AbstractServerFactory) config.getServerFactory();
// disable all default exception mappers
sf.setRegisterDefaultExceptionMappers(false);
// register your own ConstraintViolationException mapper
env.jersey().register(MyConstraintViolationExceptionMapper.class)
// restore other default exception mappers
env.jersey().register(new LoggingExceptionMapper<Throwable>() {});
env.jersey().register(new JsonProcessingExceptionMapper());
env.jersey().register(new EarlyEofExceptionMapper());
}
I think it's more reliable than a config file. And as you can see it also enables back all other default exception mappers.
ConstraintViolationExceptionMapper is the one which uses that method. In order to override it, you need to deregister it and register your own ExceptionMapper.
Remove the exception mapper(s)
Dropwizard 0.8
Add the following to your yaml file. Note that it will remove all the default exception mappers that dropwizard adds.
server:
registerDefaultExceptionMappers: false
Dropwizard 0.7.x
environment.jersey().getResourceConfig().getSingletons().removeIf(singleton -> singleton instanceof ConstraintViolationExceptionMapper);
Create and add your own exception mapper
public class ConstraintViolationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException exception) {
// get the violation errors and return the response you want.
}
}
and add your exception mapper in your application class.
public void run(T configuration, Environment environment) throws Exception {
environment.jersey().register(ConstraintViolationExceptionMapper.class);
}
#ValidationMethod should be useful here. isn't it?
http://www.dropwizard.io/0.9.0/docs/manual/validation.html
#ValidationMethod(message="Password cannot be empty")
#JsonIgnore
public boolean isPasswordProvided() {
return false if password not provided;
}