How to handle multiple fallback values in Spring Expression Language - java

Question is : how can I handle a chain of fallback values in Spring expression, allowing me to fallback on higher level configuration until I got a defined value ?
To explain a little, let's illustrate this with my own use case : I was intending to create a Spring application using the #Scheduled annotation to run some processes. Thing is I have many scheduled tasks and I would let the running frequency to be easily configurable for all of them, or only for a subset.
So I was looking for something like
#Component
public class SpecificTaskRunner {
#Scheduled(cron = "${ specific-task-cron ?: task-families-cron ?: default-cron }")
public void specificTask() {
doSomething();
}
}
Letting application configure either the frequency of all the Scheduled task at once by overriding the default-cron value, or only a subset of them by overriding the task family property, or finally, specifying on a task basis. Advantage of this approach is that it allows to play with multiple configuration levels as every scheduled task looks for the appropriate property starting from the most specific, and looking for a more generic until it got something defined, ultimately fall-backing on a default global value.
Unfortunately... this do not work. When evaluated, if the first element is not defined, then if fallback on the whole remaining. In this example, it would mean that if configuration misses a value for specific-task-cron, then the resolved value is : task-families-cron ?: default-cron which unfortunately is not what I'm looking for!

I found 2 way to handle it :
The first one, is purely based on Spring expression. Spring seems to re-evaluates the result of the Elvis operator if it also is a Spring expression. So my working configuration was :
#Component
public class SpecificTaskRunner {
#Scheduled(cron = "${ specific-task-cron ?: ${ task-families-cron ?: default-cron }}")
public void specificTask() {
doSomething();
}
}
Which in my real use case, with one more fallback, was not really convenient...
So I switched to a simpler solution that relies on configuration by defining a chain of values this way :
#application.yml
default-cron: '0 */5 0 0 0 0' #every 5 minutes
task-families-cron: ${default-cron}
specific-task-cron: ${task-families-cron}
Combined with
#Component
public class SpecificTaskRunner {
#Scheduled(cron = "${specific-task-cron}")
public void specificTask() {
doSomething();
}
}
This way any override of a property apply to hierarchical sub-levels, unless they got themselves overridden.
Both solutions seems to works, so at the end, yes Spring expression language handle multiple fallback. Whether it should be used, or is the configuration approach better readable... I let you decide.

Related

Springboot-- How to use #Profile with value having regular expression?

I have a class in my springboot maven project. I want to annotate Class with #Profile in a way that this class should not be configured for any profile ending with '-test'.
ConfigureData class is under /main.
Under my /test directory, I have couple of tests classes annotated with #ActiveProfiles as 'unit-test', 'integration-test', 'functional-test', 'system-test' etc.
I am trying something like below to ignore auto configuration of 'ConfigureData' class for all the profiles ending with '-test', but it is not working!
Is there a way to achieve this using some regular expression without specifying name of each and every test profile under #Profile?
#Configuration
#Profile("!.*-test")
public class ConfigureData {
}
I can't say for SPeL I know that it supports Regex with "matches" (special word), however, I think its too fragile, so I propose another way: Use Conditional with custom condition.
In fact #Profile introduced in Spring 3.x was rewritten in spring 4 to be implemented with a more flexible "Conditional approach".
Take a look at the source code of profile annotation:
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Conditional(ProfileCondition.class)
public #interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
Note that Profile is implemented as "Conditional" and it has a custom condition that examines all the currently running profiles and decides whether condition matches or not.
Here is an implementation of Condition in Spring 4:
class ProfileCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
}
return true;
}
}
So you can create your own annotation like #Production annotate with your custom Condition that checks the profiles and if the're test - do not match or something.
Then put #Production on the configuration (class ConfigureData in the question) and you're done!
I don't think wild card is supported in profile expression. Multiple profiles can be mentioned within #Profile sticking to have only supported operators.
Spring official documentation says,
The following operators are supported in profile expressions:
! - A logical not of the profile
& - A logical and of the profiles
| - A logical or of the profiles
The complete detail can be found here,
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/env/Profiles.html#of-java.lang.String...-

Is it possible to disable spring batch's skip-limit?

I am looking for a way to disable spring batch's skip-limit, I tried specifying a value of "0" for skip-limit, but is not accepting. Here is my use case:
My batch.xml has skip-limit="${limit_val}"
I want to set ${limit_val} to 0, meaning fail on any exception by default. On seeing the failure, I can look at the reason and decide if its ok to skip the record. If skippable, I want to be able to override ${limit_val} with a value of, say 1 and re-run the batch.
Any help on how this can be achieved?
Have you tried using skip-policy property?
In chunk under step define skip-policy="mySkipPolicy" and then give definition of skipPolicy bean.
Provide skipPolicy bean implementation by implementing SkipPolicy Interface.
public class MySkipPolicy implements SkipPolicy
{
private int limit;
public boolean shouldSkip(final Throwable t, final int skipCount) throws SkipLimitExceededException
{
return true or false based on skipCount;
}
}

Getting single NonNull value using Spring Data

I'd like to have a method in my Repository that returns a single value.
Like this:
TrainingMode findByTrainingNameAndNameEng( String trainingName, String nameEng );
http://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/
Spring Data Docs describe that in this case the method can return null if no entity is found.
I'd like to throw an exception with generic message like No TrainingMode found by %trainingName% and %nameEng% or smth like that.
I can use Optional<TrainingMode> as a return value and then use orElseThrow
Optional<TrainingMode> findByTrainingNameAndNameEng( String trainingName, String nameEng );
repository.findByTrainingNameAndNameEng(name, nameEng).orElseThrow(() -> new RuntimeException(...));
But I should call this method each time when this method is called. It's not clear - DRY priciple is broken.
How to get nonnull single value with orElseThrow using Spring Data?
The DRY principle would be violated if you duplicate null handling throughout the application logic where it is being invoked. If DRY principle is the thing you are worried the most then i can think of:
You can make a "Service" class which would delegate calls to annotated repository and handle null response logic to it, and use that service class instead of calling repositories directly. Drawback would be introducing another layer to your application (which would decouple repositories from your app logic).
There is possibility of adding custom behavior to your data repositories which is described in "3.6.1. Adding custom behavior to single repositories" section of documentation. Sorry for not posting the snippet.
The issue I personally have with second approach is that it pollutes app with interfaces, enforces you to follow a certain naming patterns (never liked 'Impl' suffixes), and might make migrating code a bit more time consuming (when app becomes big it becomes harder to track which interface is responsible for which custom behavior and then people just simply start creating their own behavior which turns out to be duplicate of another).
I found a solution.
First, Spring Data processes getByName and findByName equally. And we can use it: in my case find* can return null (or returns not null Optional, as you wish) and get* should return only value: if null is returned then exception is thrown.
I decided to use AOP for this case.
Here's the aspect:
#Aspect
#Component
public class GetFromRepositoryAspect {
#Around("execution(public !void org.springframework.data.repository.Repository+.get*(..))")
public Object aroundDaoMethod( ProceedingJoinPoint joinpoint ) throws Throwable {
Object result = joinpoint.proceed();
if (null == result) {
throw new FormattedException( "No entity found with arhs %s",
Arrays.toString( joinpoint.getArgs() ) );
}
return result;
}
}
That's all.
You can achieve this by using the Spring nullability annotations. If the method return type is just some Entity and it's not a wrapper type, such as Optional<T>, then org.springframework.dao.EmptyResultDataAccessException will be thrown in case of no results.
Read more about Null Handling of Repository Methods.

Spring security authrorize based on input parameter criteria

I have a scenario where I need to authorize user based on combination of his permission and input parameter passed.
this is the current scenario
public void bookTicket(String bookingType)
{
if (bookingType == "AIR"){
bookAirTicket();
}else{
bookBusTicket();
}
}
#PreAuthorize("hasRole('BOOK_AIR')")
private void bookAirTicket(){
}
#PreAuthorize("hasRole('BOOK_BUS')")
private void bookBusTicket(){
}
Can we have some thing like
#PreAuthorize(("hasRole('BOOK_AIR')" AND bookinType='AIR') OR ("hasRole('BOOK_BUS')" AND bookinType='BUS'))
public void bookTicket(String bookingType)
{
if (bookingType == "AIR"){
bookAirTicket();
}else{
bookBusTicket();
}
}
Basically I need authorization based in input parameters
Thanks
Yes, you can. Parameters can be accessed as Spring EL variables. In fact the reference manual gives several examples which use method parameters. The class needs to be compiled with debug symbols present (which is usually the case).
Note that the annotation value is a single expressions string:
"(hasRole('BOOK_AIR') and #bookinType == 'AIR') or (hasRole('BOOK_BUS') and #bookinType='BUS')"
In practice, using complicated expressions is rather error-prone. You could also use a simpler expression, something like
"#accessChecker.check('book', #bookinType)"
Where accessChecker is a bean in your application context with a "check" method which returns true or false depending on whether the supplied operation information is allowed (you can check the current user's roles by accessing the security context yourself - you'll find that discussed elsewhere on SO).
You could also look into writing your own AccessDecisionManager or AccessDecisionVoter and plugin the functionality there, but that requires more internal knowledge.

Play: Using a configuration property as the value of an annotation

This (obviously) works:
#Every("10s")
public class Extinguisher extends Job {
...
}
...but this doesn't:
#Every(Play.configuration.getProperty("my.setting", "10s"))
public class Extinguisher extends Job {
...
}
When running auto-test, the app doesn't start and complains my controllers can't get enhanced because of a NullPointerException encountered by javassist.
Is there a way to configure a job scheduling from application.conf?
You can schedule your job manually from #OnApplicationStartup job:
#OnApplicationStartup
public class ExtinguisherBootstrap extends Job {
public void doJob() {
new Extinguisher()
.every(Play.configuration.getProperty("my.setting", "10s"));
}
}
I don't know whether Play or javassist extend what you can do with the Java language, but I can at least point out that the following line is not legal Java:
#Every(Play.configuration.getProperty("my.setting", "10s"))
For an annotation with a parameter with type T and value V, the Java Language Specification requires that:
If T is a primitive type or String, V is a constant expression
In this case, T, the type of the annotation parameter, is a String, but the value you're trying to set to it isn't a (compile-time) constant expression.
The issue is that "configuration" wont be available at that stage.
I don't think what you want to do is possible (as per my current knowledge of Play, maybe someone knows a trick to make it work)
You may be able to "hack it" by having a job run every few seconds and in that job launch the target job as per configuration. It's less efficient, but it may solve your issue
You can do something like this:
#On("cron.noon")
Which will look for a line like this in application.conf:
cron.noon = 1s

Categories