I have a Spring Boot application with a YAML configuration that contains a feature list like this:
features:
- key: feature1
enabled: true
description: First feature
- key: feature2
enabled: false
description: Second feature
...
I would like to use #ConditionalOnExpression to conditionally initialize beans related to those features, identifying them by keys. Since "features" property is a list, it seems I need collection selection to do this. I have tried these two options for the annotation's value:
#ConditionalOnExpression("${features.?[key == 'feature1'][0].enabled}")
#ConditionalOnExpression("${features}.?[key == 'feature1'][0].enabled")
But both give the same error on startup:
org.springframework.expression.spel.SpelParseException: EL1041E: After parsing a valid expression, there is still more data in the expression: 'lcurly({)'
If I pass the expression (without ${}) to SpelExpressionParser.parseExpression() and then evaluate it (against a list of feature objects built programmatically), it works as expected and returns the value of "enabled" property. So the expression's structure seems to be OK, and the problem is how I use it in #ConditionalOnExpression. What exactly can I be doing wrong?
Spring SpEL supports only simple property placeholders inside the properties file.
If your properties file and condition code are like this, your SpEL will work:
features:
feature1:
enabled: true
description: First feature.
feature2:
enabled: false
description: Second feature.
#ConditionalOnExpression(
value = "#{'${features.feature1.enabled}'.equals('true')}"
)
But if you want to load it as a map feature and use the map SpEL features;
The most elegant way is writing the key-values as an inline JSON (use ' and " chars to avoid cumbersome escaping) and parsing it using SpEL:
features: '{
"feature1": {"enabled":"true", "description":"..."},
"feature2": {"enabled":"false", "description":"..."}
}'
#ConditionalOnExpression(
value = "#{${features}['feature1']['enabled'].equals('true')}"
)
If you had used the selection method it would have looked like this;
#ConditionalOnExpression(
value = "#{${features}.?[key=='feature1']['feature1']['enabled'].equals('true')}"
)
${...} has nothing to do with SpEL; that syntax is for property placeholders in Spring.
e.g. #Value("${property.foo:default}" String someProperty.
This will set someProperty to the value of property.foo if present, or default otherwise. (the :... part can be omitted to force the property to exist).
Related
Intro
I have a Spring Boot application with its configuration set up with application.yml file like so:
Scala class:
#ConfigurationProperties(prefix = "foo.bar")
#ConstructorBinding
case class MyConfig(
biz: String,
testYamlMaps: java.util.Map[String, String]
)
application.yml:
foo:
bar:
biz: "hello"
test-yaml-maps: ${YAML_TEST_MAP} # option 1
# test-yaml-maps: {redelivery: true, secure: false} option 2
YAML_TEST_MAP environment variable:
$ echo $YAML_TEST_MAP
{redelivery: true, secure: false}
Problem
Option 2 works but with option 1 it looks like Spring is parsing application.yml first and only then substitutes environment values this way treating YAML_TEST_MAP as a String making it throw this error:
No converter found capable of converting from type [java.lang.String] to type [java.util.Map<java.lang.String, java.lang.String>]
Questions
Is there a way to force Spring replace environment variables first and only then parse?
Is there a way to tell Spring to treat value as a rest of yaml document?
I'm learning creating rest Api using spring boot. FOr reference I was checking some existing code where in yaml file I found two parameters mentioned below
name: "name"
in: "query"
description: "doing something"
x-phase-dataelem: "CONF//Y/N"
required: false
schema:
type: string
maxlength:15
name: "tame"
in: "header"
description: "doing something something"
x-phase-dataelem: "CONF//Y/N"
required: true
schema:
type: string
maxlength:15
am literally not able to understand these parameters
in: "query"
in: "header"
x-phase-dataelem: "CONF//Y/N"
I know that, these are some values which are being passed to client url to process, but not able to understand these parameters. what's significance of using these 3 parameters ?
can anyone help ?
This YAML snippet looks like a Swagger/OpenAPI contract. You can find more about OpenAPi and read its specification here - https://swagger.io/specification/
in describes the location of the HTTP parameter.
Quote from the OpenAPI specification:
There are four possible parameter locations specified by the in
field:
path - Used together with Path Templating, where the parameter value is actually part of the operation's URL. This does not include
the host or base path of the API. For example, in /items/{itemId}, the
path parameter is itemId.
query - Parameters that are appended to the URL. For example, in /items?id=###, the query parameter is id.
header - Custom headers that are expected as part of the request. Note that RFC7230 states header names are case insensitive.
cookie - Used to pass a specific cookie value to the API.
Regarding the x-phase-dataelem, it is a custom extension in your OpenAPI contract. It is used for providing some additional metadata/information/properties about the described items (including parameters).
Quote from the OpenAPI specification:
While the OpenAPI Specification tries to accommodate most use cases,
additional data can be added to extend the specification at certain
points.
The extensions properties are implemented as patterned fields that are always prefixed by x-, for example, x-internal-id. The value can be null, a primitive, an array or an object. Can have any valid JSON format value.
Given a appplication.config which contains fixed values and optional overwrites as for example like this:
timeout.seconds = 30
timeout.seconds=${?SSO_TIMEOUT_SECONDS}
using com.typesafe.config
which function does return the config with fully parsed entries?
e.g
timeout.seconds = 99
if it has been set externally otherwise the default value.
NOT returned should be the config with preset values AND optional replacements.
I tested
ConfigFactory.defaultApplication()
but that does return both. Although the description makes me think it would not.
You can load the default config using
ConfigFactory.load()
This will replace any substitutions with the appropriate values.
In Spring 4, using the #Value annotation, what is the right way to specify a system property as a default if a specified property does not exists?
Whereas this works for the no-default case:
#Value("${myapp.temp}")
private String tempDirectory;
This doesn't work when I need a default:
#Value("#{myapp.temp ?: systemProperties.java.io.tmpdir}")
private String tempDirectory;
Nor does this:
#Value("#{myapp.temp ?: systemProperties(java.io.tmpdir)}")
private String tempDirectory;
Both of these give me an exception at the time Spring is trying to create the bean:
org.springframework.beans.factory.BeanCreationException: Error creating bean
with name 'configurationService': Invocation of init method failed;
nested exception is java.lang.NullPointerException
Can this be done?
I tried the following and it worked for me:
#Value("${myapp.temp:#{systemProperties['java.io.tmpdir']}}")
private String tempDirectory;
The missing parts for you I believe was not using ?: and needing the #{}. According to this answer:
${...} is the property placeholder syntax. It can only be used to dereference properties.
#{...} is SpEL syntax, which is far more capable and complex. It can also handle property placeholders, and a lot more besides.
So basically what is happening is we are telling Spring to first interpret myapp.temp as property placeholder syntax by using the ${} syntax. We then use : instead of ?: (which is called the Elvis operator) since the elvis operator only applies to Spring Expression Language expressions, not property placeholder syntax. The third part of our statement is #{systemProperties['java.io.tmpdir']} which is telling Spring to interpret the next expression as a Spring Expression and allows us to get system properties.
Try systemProperties['java.io.tmpdir'].
It's a map, so if the key has a dot in the name, you should use [..]
For me it only works with different property-names (being property.name.a a key with a value in my application.properties and property.name.b a Environment-Variable) like:
#Value("${property.name.a:${property.name.b}}")
The same names didn´t work for me like expected (loading the default, when the first property isn´t present), e.g.:
#Value("${property.name.a:${property.name.a}}")
I have following code in my tml file:
<t:loop source="navItem.subPages" value="var:subPage">
<t:any element="li" class="prop:classForPageName">
<t:pagelink page="var:subPage">${getMenuPageName(var:subPage)}</t:pagelink>
</t:any>
</t:loop>
I have a problem to pass a variable var:subPage to method ${getMenuPageName(var:subPage)}, as this throws an exception:
Could not convert 'getMenuPageName(var:subPage)' into a component parameter binding: Error parsing property expression 'getMenuPageName(var:subPage)': line 1:15 no viable alternative at input '('.
You can't use binding prefixes (like var:) inside property expressions.
You may only use prefix in front of the expression to let Tapestry know how it should interpret the remainder (the part after the colon).
Refer to NBF grammar for property expressions to see what's allowed inside:
Tapestry Documentation > User Guide > Property Expressions.
Property expressions were created to support just very basic constructs. If you need more complex expressions you should create corresponding methods in your java class and refer to them using the prop: binding prefix.
Template expansions you've mentioned (${...}) work the same as parameter bindings:
Under the covers, expansions are the same as parameter bindings. The
default binding prefix for expansions is "prop:" (that is, the name of
a property or a property expression), but other binding prefixes are
useful, especially "message:" (to access a localized message from the
component's message catalog).