How to prevent a property beeing set on certain profile - spring

we want to implement a feature for enabling a user to choose a role in the system, by sending the role he/she wishes to have in the login request.
this feature is meant for testing (creating test-users or assigning roles to existing ones is "impossible" in the customers system) and, of course, should never be deployed to a production environment.
I want to deployment of my application to fail if the property feature.choose-role is set to true AND the spring active profile is set to production.
As we are using springs config-server features, i also want to application to completely stop working if the property is set to true at runtime.
My first attempt was to simply create this Config:
#Configuration
public class FeatureToggleGuardConfig {
#Bean
#RefreshScope
#ConditionalOnProperty(value = "feature.choose-roles", havingValue = "true")
#Profile("production")
public Object preventDeploymentOfRoleChoosingFeatureOnProduction() {
throw new RuntimeException("feature.choose-roles must not be true in production profile!");
}
}
This works if the property is set to true at deployment, but as i understand, will only attempt to refresh the bean if someone actually tries to use it - which will never happen.
Also - i don't think that it would stop the whole application if this just threw a runtime exception when it is used.
in short:
I want to prevent my application to run (or keep running) if at any time, the property feature.choose-roles is true and the active profile is "production".
I do not want to alter production code in order to do this ( if(feature is enables && profile is production) etc.)

Perhaps instead of having a your profile drive some sort of blocker, you can have your profile drive a config bean which says whether or not to use the feature. Then, have the nonProd config read from your property, and have the prod config always return false.
Something like:
public interface ChooseRolesConfig {
boolean allowChoosingRoles();
}
#Profile("!production")
public class NonProdChooseRolesConfig implements ChooseRolesConfig {
#Value("${feature.choose-roles}")
boolean chooseRoles;
#Override
public boolean allowChoosingRoles() {
return chooseRoles;
}
}
#Profile("production")
public class ProdChooseRolesConfig implements ChooseRolesConfig {
#Override
public boolean allowChoosingRoles() {
return false;
}
}
and then just autowire a ChooseRolesConfig object, and call the method, and regardless of what you change feature.choose-roles to using cloud config, it should always be false for prod.
Disclaimer: I blindly wrote this so it might be missing some annotations or something but hopefully you get the idea

Related

#Bean method requires to return null conditionally

I have basic authentication enabled in my system and now I am trying to integrate the SAML auth using Spring-security-SAML. I have created a method that returns the RelyingPartRegistrationRepository bean. In a condition While the user-configured values are not sufficient to Create the RelyingPartRegistration, I would have to either create a RelyingPartRegistrationRepository with Empty array which is not possible because there are checks to be not empty. another option is to return null from the Bean method. which is also a failure case because context initialization will fail in that case. All I want is to either not initialize this bean or at least not prevent context initialization. So that I can at least switch back to Basic Authentication.
You can use #ConditionalOn... to selectively enable/disable particular beans, based on things like properties
class MyConfiguration {
#Bean
#ConditionalOnProperty(name = "saml-enabled", havingValue = "true")
public RelyingPartRegistrationRepository() { ... }
}
See this article on Baeldung, and another article which covers a few other conditionals which may be useful, and of course the official documentation.

Active profile in SpringBootTest based on system variable

As the host of Redis is different in local and CI, my #Tests can pass locally, they can't pass in CI.
Firstly, I tried to mock the RedisTemplate like this:
RedisTemplate redisTemplate = mock(RedisTemplate.class);
ValueOperations valueOperations = mock(ValueOperations.class);
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
when(valueOperations.increment(anyString(), anyLong())).thenReturn(1L);
when(valueOperations.get("a#a.com")).thenReturn("1");
It did mocked RedisTemplate, but can't mock redisTemplate.opsForValue() and valueOperations.increment(...) ( I can't find reason )
Then, I wrote two profiles named application-ci-test.yml and applicaiton-test.yml, tried to active one of them based on system environment variable
I learnd from here that I can set active profile in this way:
#Configuration
public class MyWebApplicationInitializer
implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setInitParameter(
"spring.profiles.active", "dev");
}
}
and this way:
#Autowired
private ConfigurableEnvironment env;
...
env.setActiveProfiles("someProfile");
But I don't know how to use them. The system variable can get by System.getenv(..). So now I want to know how to active profile based on the variable I get.
I found a way to active corresponding profile based on system variable or property:
import org.springframework.test.context.ActiveProfilesResolver;
public class SpringActiveProfileResolver implements ActiveProfilesResolver {
#Override
public String[] resolve(Class<?> testClass) {
final String isCITest = System.getEnv("isCITest");
return new String[] { isCITest == null ? "test" : "ci-test" };
}
}
then use the parameter resolver in #ActiveProiles:
#ActiveProfiles(resolver = SpringActiveProfileResolver.class)
How to set environment variable is anther issue, and answers above have already answered it
Assuming your #Test methods are in a class with the #SpringBootTest annotation, you can use #ActiveProfiles to set the profile.
#SpringBootTest
#ActiveProfiles("someProfile")
Use run parameters inside your CI job/script.
Depending how You start Your tests, You can for example do it with VM arguments
mvn test -Dspring.profiles.active=ci-test
or
java -jar -Dspring.profiles.active=ci-test
or whatever.
On the other hand you can use program arguments:
java -jar --spring.profiles.active=ci-test
One way or the other, providing active profile at start will activate property file of your choice.
If you want some specific piece of code (configuration class for example) to be run with specific profile only, annotate that piece of code with #Profile("ci-test")
Example:
#Configuration
#Profile("ci-test")
public class SomeConfiguration {
//any configuration beans etc.
}
Following class will only be loaded when Your active profile will be "ci-test". So if You run Your app on Your CI server with one of the commands above, both property file named "ci-test" and this configuration class will get loaded.
It's also worth adding that in order for some code to run in ALL profiles EXCEPT specified, you can negate the name inside profile annotation like: #Profile("!ci-test").
Code annotated like that will run with all profiles (including default) except "ci-test".

How to override a variable in Spring Boot properties file

I have an existing properties file like below
csv.requireresync=true
There are two actions in the system that will get and set value to it.
Every 5 minutes there will a process that will set the csv.requireresync to false.
Every 2 weeks there will be a process that will set the csv.requireresync to true.
I tried to do the above using #Component but not working
#Component
#ConfigurationProperties(prefix = "csv")
public class ResyncConfig {
private boolean requireresync;
public void setRequireresync(boolean requireresync) {
this.requireresync = requireresync;
}
public boolean isRequireresync() {
return requireresync;
}
}
The idea is when the I call setRequireresync to true the value must reflect in the application.properties file.
Is it possible achieve what I want ? or do I need a extra configuration file for that ?
Just set required values directly to ResyncConfig isntance. It is shared across application (if not using Scopes its singleton) and any changes made to it will be in effect. File is only for bootstraping config class with initial settings.

guice - select different providers while running

I want to change a provider I'm using at runtime without having to stop the JVM. For example, this isn't exactly what I'm trying to do, but the idea is the same: Say, I want to switch from Amazon S3 to Google Cloud storage in the middle of a running application.
Is that something I can do within guice?
I would have to have all jars available at runtime and configure all modules at startup. Then, later once the application is started, I'd have to use a provider that can determine which instance to inject # startup and later on when it changes.
Or, would it be better just to restart the application after updating a configuration and the system would then proceed with that configuration and if it needs to change, the application would again need to be restarted.
Would OSGI help here?
You don't need anything extra: Guice can do it out-of-the-box. But... you'll have to use Providers instead of the direct instance.
In your module
bind(Cloud.class)
.annotatedWith(Names.named("google"))
.to(GoogleCloud.class);
bind(Cloud.class)
.annotatedWith(Names.named("amazon"))
.to(AmazonCloud.class);
bind(Cloud.class)
.toProvider(SwitchingCloudProvider.class);
Somewhere
class SwitchingCloudProvider implements Provider<Cloud> {
#Inject #Named("google") Provider<Cloud> googleCloudProvider;
#Inject #Named("amazon") Provider<Cloud> amazonCloudProvider;
#Inject Configuration configuration; // used as your switch "commander"
public Cloud get() {
switch(configuration.getCloudName()) {
case "google": return googleCloudProvider.get();
case "amazon": return amazonCloudProvider.get();
default:
// Whatever you want, usually an exception.
}
}
}
Or in a provider method in your module
#Provides
Cloud provideCloud(
#Named("google") Provider<Cloud> googleCloudProvider,
#Named("amazon") Provider<Cloud> amazonCloudProvider,
Configuration configuration) {
switch(configuration.getCloudName()) {
case "google": return googleCloudProvider.get();
case "amazon": return amazonCloudProvider.get();
default:
// Whatever you want, usually an exception.
}
}
Usage
class Foo {
#Inject Provider<Cloud> cloudProvider; // Do NOT inject Cloud directly or you won't get the changes as they come up.
public void bar() {
Cloud cloud = cloudProvider.get();
// use cloud
}
}

autowire a Spring bean based on the value of a property

nI am developing a Spring MVC web app using Spring 3.2. We will deploy the web app to different customers. Each customer may use one of several implementations of a service interface.
It's possible that the customer may need to reset these values, so we can't just hard-wire the implementation into the application, it needs to be externally configurable.
We are already using customer-specific property files that for setting simple properties such as Strings, numbers etc, but I'm asking how to set a particular implementation of an interface.
E.g.,
class MyClass {
// this is straightforward
#Value("${customer.propertyInPropertyFile}")
private String customerSpecificString;
// how to set the correct implementation for each customer?
private ISomeService service;
}
If there are 4 implementations of ISomeService, we can't autowire, or explicitly set a bean, as this will then be set in the compiled code - and it needs to be configurable after the application is deployed ( it would be OK to restart the application though if need be)..
Does anyone know how to do this? Would this better be performed using Spring EL, or profiles?
Thanks!
So, as I wanted to used Java configuration, I used the following solution:
#Configuration
#Profile("prod")
#EnableAsync
public class ProductionConfig extends BaseConfig
// inject property value which identifies theimplementation to use
Value("${service.impl}")
private String serviceName;
#Bean()
public IRepository repository() {
IRepository rc = null;
if(StringUtils.isEmpty(serviceName)){
rc = new Impl1();
} else if ("sword-mets".equals(serviceName)){
rc = new Impl2();
} else {
rc = new Impl3();
}
log.info("Setting in repository implementation " + rc);
return rc;
}
So, this isn't quite as clean as the suggested reply using aliases - the Config class needs to know about all the possible implementation classes - but is simple and avoids having to use the XML config just for this bean.

Resources