Spring: How to do AND in Profiles? - java

Spring Profile annotation allows you to select profiles. However if you read documentation it only allows you to select more than one profile with OR operation. If you specify #Profile("A", "B") then your bean will be up if either profile A or profile B is active.
Our use case is different we want to support TEST and PROD versions of multiple configurations. Therefore sometimes we want to autowire the bean only if both profiles TEST and CONFIG1 are active.
Is there any way to do it with Spring? What would be the simplest way?

Since Spring 5.1 (incorporated in Spring Boot 2.1) it is possible to use a profile expression inside profile string annotation. So:
In Spring 5.1 (Spring Boot 2.1) and above it is as easy as:
#Component
#Profile("TEST & CONFIG1")
public class MyComponent {}
Spring 4.x and 5.0.x:
Approach 1: answered by #Mithun, it covers perfectly your case of converting OR into AND in your profile annotation whenever you annotate the Spring Bean also with his Condition class implementation. But I want to offer another approach that nobody proposed that has its pro's and con's.
Approach 2:
Just use #Conditional and create as many Condition implementations as combinations needed. It has the con of having to create as many implementations as combinations but if you don't have many combinations, in my opinion, it is a more concise solution and it offers more flexibility and the chance of implementing more complex logical resolutions.
The implementation of Approach 2 would be as follows.
Your Spring Bean:
#Component
#Conditional(value = { TestAndConfig1Profiles.class })
public class MyComponent {}
TestAndConfig1Profiles implementation:
public class TestAndConfig1Profiles implements Condition {
#Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
return context.getEnvironment().acceptsProfiles("TEST")
&& context.getEnvironment().acceptsProfiles("CONFIG1");
}
}
With this approach you could easily cover more complex logical situations like for example:
(TEST & CONFIG1) | (TEST & CONFIG3)
Just wanted to give an updated answer to your question and complement other answers.

Since Spring does not provide the AND feature out of the box. I would suggest the following strategy:
Currently #Profile annotation has a conditional annotation #Conditional(ProfileCondition.class). In ProfileCondition.class it iterates through the profiles and checks if the profile is active. Similarly you could create your own conditional implementation and restrict registering the bean. e.g.
public class MyProfileCondition implements Condition {
#Override
public boolean matches(final ConditionContext context,
final AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
final MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (final Object value : attrs.get("value")) {
final String activeProfiles = context.getEnvironment().getProperty("spring.profiles.active");
for (final String profile : (String[]) value) {
if (!activeProfiles.contains(profile)) {
return false;
}
}
}
return true;
}
}
return true;
}
}
In your class:
#Component
#Profile("dev")
#Conditional(value = { MyProfileCondition.class })
public class DevDatasourceConfig
NOTE: I have not checked for all the corner cases (like null, length checks etc). But, this direction could help.

A little bit improved version of #Mithun answer:
public class AndProfilesCondition implements Condition {
public static final String VALUE = "value";
public static final String DEFAULT_PROFILE = "default";
#Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() == null) {
return true;
}
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs == null) {
return true;
}
String[] activeProfiles = context.getEnvironment().getActiveProfiles();
String[] definedProfiles = (String[]) attrs.getFirst(VALUE);
Set<String> allowedProfiles = new HashSet<>(1);
Set<String> restrictedProfiles = new HashSet<>(1);
for (String nextDefinedProfile : definedProfiles) {
if (!nextDefinedProfile.isEmpty() && nextDefinedProfile.charAt(0) == '!') {
restrictedProfiles.add(nextDefinedProfile.substring(1, nextDefinedProfile.length()));
continue;
}
allowedProfiles.add(nextDefinedProfile);
}
int activeAllowedCount = 0;
for (String nextActiveProfile : activeProfiles) {
// quick exit when default profile is active and allowed profiles is empty
if (DEFAULT_PROFILE.equals(nextActiveProfile) && allowedProfiles.isEmpty()) {
continue;
}
// quick exit when one of active profiles is restricted
if (restrictedProfiles.contains(nextActiveProfile)) {
return false;
}
// just go ahead when there is no allowed profiles (just need to check that there is no active restricted profiles)
if (allowedProfiles.isEmpty()) {
continue;
}
if (allowedProfiles.contains(nextActiveProfile)) {
activeAllowedCount++;
}
}
return activeAllowedCount == allowedProfiles.size();
}
}
Was unable to post it in the comments.

Yet another option is to play on the Class/Method level allowed by the #Profile annotation. Not as flexible as implementing MyProfileCondition but quick and clean if it suits your case.
e.g. this won't start when FAST & DEV are both active, but will if only DEV is:
#Configuration
#Profile("!" + SPRING_PROFILE_FAST)
public class TomcatLogbackAccessConfiguration {
#Bean
#Profile({SPRING_PROFILE_DEVELOPMENT, SPRING_PROFILE_STAGING})
public EmbeddedServletContainerCustomizer containerCustomizer() {

Another kind of trick but might work in many scenarios is put #Profile annotation on #Configuration and the other #Profile on #Bean - that creates logical AND between 2 profiles in java-based spring config.
#Configuration
#Profile("Profile1")
public class TomcatLogbackAccessConfiguration {
#Bean
#Profile("Profile2")
public EmbeddedServletContainerCustomizer containerCustomizer() {

If you have already marked a configuration class or bean method with #Profile annotation, it is simple to check for additional profiles (e.g. for AND condition) with Environment.acceptsProfiles()
#Autowired Environment env;
#Profile("profile1")
#Bean
public MyBean myBean() {
if( env.acceptsProfiles("profile2") ) {
return new MyBean();
}
else {
return null;
}
}

I improved #rozhoc's answer since that answer did not account for the fact that no profile is equivalent to 'default' when it comes to using #Profile. Also, conditions that I wanted were !default && !a which #rozhoc's code did not handle properly. Finally I used some Java8 and show only the matches method for brevity.
#Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() == null) {
return true;
}
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs == null) {
return true;
}
Set<String> activeProfilesSet = Arrays.stream(context.getEnvironment().getActiveProfiles()).collect(Collectors.toSet());
String[] definedProfiles = (String[]) attrs.getFirst(VALUE);
Set<String> allowedProfiles = new HashSet<>(1);
Set<String> restrictedProfiles = new HashSet<>(1);
if (activeProfilesSet.size() == 0) {
activeProfilesSet.add(DEFAULT_PROFILE); // no profile is equivalent in #Profile terms to "default"
}
for (String nextDefinedProfile : definedProfiles) {
if (!nextDefinedProfile.isEmpty() && nextDefinedProfile.charAt(0) == '!') {
restrictedProfiles.add(nextDefinedProfile.substring(1, nextDefinedProfile.length()));
continue;
}
allowedProfiles.add(nextDefinedProfile);
}
boolean allowed = true;
for (String allowedProfile : allowedProfiles) {
allowed = allowed && activeProfilesSet.contains(allowedProfile);
}
boolean restricted = true;
for (String restrictedProfile : restrictedProfiles) {
restricted = restricted && !activeProfilesSet.contains(restrictedProfile);
}
return allowed && restricted;
}
Here is how you actually use it in case that was confusing as well:
#Profile({"!default", "!a"})
#Conditional(value={AndProfilesCondition.class})

Related

Call different proxyied method from Spring Aspect

I have two different aspects. How do I ensure that when calling method from one aspect, it will still go through proxy chain?
Here is relevant code:
Inner aspect:
#Around("withinReplicatedRepository() && entityMethod() && insertMethod()")
public Object trackInsert(ProceedingJoinPoint jp) throws Throwable {
return trackChange(jp, ChangeType.INSERT, jp.getArgs()[0]);
}
Outer aspect:
#Around("withinReplicatedRepository() && entityMethod() && autoSaveRepository() && saveMethod()")
public Object saveEntity(ProceedingJoinPoint jp) throws Throwable {
TransactionUtil.ensureTransactional();
Object entity = jp.getArgs()[0];
AutoSaveRepository repository = (AutoSaveRepository)jp.getTarget();
if (repository.exists(entity)) {
repository.update(entity);
} else {
repository.insert(entity);
}
return null;
}
Usage:
AutoSaveRepository<MyEntity> repo = ...;
repo.save(entity);
My problem is that jp.getTarget() will return original class, thus repository.insert() will not be captured by trackInsert.
You can try
AopContext.currentProxy()
See Javadoc.
The prerequisite is that you activate proxy exposure, though:
In XML you can do this since Spring 3.0.3 via<aop:aspectj-autoproxy expose-proxy="true"/>.
In annotation-style config your can do it since 4.3.1 via #EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true),see Javadoc

Set jvmRoute in spring boot 2.0.0

For sticky session i need to set the jvmRoute of the embedded tomcat.
Actually only a
System.setProperty("jvmRoute", "node1");
is required, but i want to set a via application.properties configurable property. I don't know how and when to set this with #Value annotated property.
With #PostConstruct as described here, it does not work (at least not in spring boot 2.0.0.RELEASE)
The only way i found so far is
#Component
public class TomcatInitializer implements ApplicationListener<ServletWebServerInitializedEvent> {
#Value("${tomcat.jvmroute}")
private String jvmRoute;
#Override
public void onApplicationEvent(final ServletWebServerInitializedEvent event) {
final WebServer ws = event.getApplicationContext().getWebServer();
if (ws instanceof TomcatWebServer) {
final TomcatWebServer tws = (TomcatWebServer) ws;
final Context context = (Context) tws.getTomcat().getHost().findChildren()[0];
context.getManager().getSessionIdGenerator().setJvmRoute(jvmRoute);
}
}
}
It works, but it does not look like much elegant...
Any suggestions are very appreciated.
You can customise Tomcat's Context a little more elegantly by using a context customiser. It's a functional interface so you can use a lambda:
#Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
return (tomcat) -> tomcat.addContextCustomizers((context) -> {
Manager manager = context.getManager();
if (manager == null) {
manager = new StandardManager();
context.setManager(manager);
}
manager.getSessionIdGenerator().setJvmRoute(jvmRoute);
});
}
I'm using Spring Boot 2.0.4. The above answer did not work for me all the way. I had to update it this way:
#Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> servletContainer() {
return (tomcat) -> {
tomcat.addContextCustomizers((context) -> {
Manager manager = context.getManager();
if (manager == null) {
manager = new StandardManager();
context.setManager(manager);
}
((ManagerBase) context.getManager()).getEngine().setJvmRoute("tomcatJvmRoute");
});
};
}

#ConditionalOnProperty for lists or arrays?

I'm using Spring Boot 1.4.3 #AutoConfiguration where I create beans automatically based on properties user specifies. User can specify an array of services, where name and version are required fields:
service[0].name=myServiceA
service[0].version=1.0
service[1].name=myServiceB
service[1].version=1.2
...
If the user forgets to specify a required field on even just one service, I want to back-off and not create any beans. Can I accomplish this with #ConditionalOnProperty? I want something like:
#Configuration
#ConditionalOnProperty({"service[i].name", "service[i].version"})
class AutoConfigureServices {
....
}
This is the custom Condition I created. It needs some polishing to be more generic (ie not hardcoding strings), but worked great for me.
To use, I annotated my Configuration class with #Conditional(RequiredRepeatablePropertiesCondition.class)
public class RequiredRepeatablePropertiesCondition extends SpringBootCondition {
private static final Logger LOGGER = LoggerFactory.getLogger(RequiredRepeatablePropertiesCondition.class.getName());
public static final String[] REQUIRED_KEYS = {
"my.services[i].version",
"my.services[i].name"
};
#Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
List<String> missingProperties = new ArrayList<>();
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(context.getEnvironment());
Map<String, Object> services = resolver.getSubProperties("my.services");
if (services.size() == 0) {
missingProperties.addAll(Arrays.asList(REQUIRED_KEYS));
return getConditionOutcome(missingProperties);
}
//gather indexes to check: [0], [1], [3], etc
Pattern p = Pattern.compile("\\[(\\d+)\\]");
Set<String> uniqueIndexes = new HashSet<String>();
for (String key : services.keySet()) {
Matcher m = p.matcher(key);
if (m.find()) {
uniqueIndexes.add(m.group(1));
}
}
//loop each index and check required props
uniqueIndexes.forEach(index -> {
for (String genericKey : REQUIRED_KEYS) {
String multiServiceKey = genericKey.replace("[i]", "[" + index + "]");
if (!resolver.containsProperty(multiServiceKey)) {
missingProperties.add(multiServiceKey);
}
}
});
return getConditionOutcome(missingProperties);
}
private ConditionOutcome getConditionOutcome(List<String> missingProperties) {
if (missingProperties.isEmpty()) {
return ConditionOutcome.match(ConditionMessage.forCondition(RequiredRepeatablePropertiesCondition.class.getCanonicalName())
.found("property", "properties")
.items(Arrays.asList(REQUIRED_KEYS)));
}
return ConditionOutcome.noMatch(
ConditionMessage.forCondition(RequiredRepeatablePropertiesCondition.class.getCanonicalName())
.didNotFind("property", "properties")
.items(missingProperties)
);
}
}
Old question, but I hope my answer will help for Spring2.x:
Thanks to #Brian, I checked migration guide, where I was inspired by example code. This code works for me:
final List<String> services = Binder.get(context.getEnvironment()).bind("my.services", List.class).orElse(null);
I did try to get List of POJO (as AutoConfigureService) but my class differs from AutoConfigureServices. For that purpose, I used:
final Services services = Binder.get(context.getEnvironment()).bind("my.services", Services.class).orElse(null);
Well, keep playing :-D
Here's my take on this issue with the use of custom conditions in Spring autoconfiguration. Somewhat similar to what #Strumbels proposed but more reusable.
#Conditional annotations are executed very early in during the application startup. Properties sources are already loaded but ConfgurationProperties beans are not yet created. However we can work around that issue by binding properties to Java POJO ourselves.
First I introduce a functional interface which will enable us to define any custom logic checking if properties are in fact present or not. In your case this method will take care of checking if the property List is empty/null and if all items within are valid.
public interface OptionalProperties {
boolean isPresent();
}
Now let's create an annotation which will be metannotated with Spring #Conditional and allow us to define custom parameters. prefix represents the property namespace and targetClass represents the configuration properties model class to which properties should be mapped.
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Conditional(OnConfigurationPropertiesCondition.class)
public #interface ConditionalOnConfigurationProperties {
String prefix();
Class<? extends OptionalProperties> targetClass();
}
And now the main part. The custom condition implementation.
public class OnConfigurationPropertiesCondition extends SpringBootCondition {
#Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
MergedAnnotation<ConditionalOnConfigurationProperties> mergedAnnotation = metadata.getAnnotations().get(ConditionalOnConfigurationProperties.class);
String prefix = mergedAnnotation.getString("prefix");
Class<?> targetClass = mergedAnnotation.getClass("targetClass");
// type precondition
if (!OptionalProperties.class.isAssignableFrom(targetClass)) {
return ConditionOutcome.noMatch("Target type does not implement the OptionalProperties interface.");
}
// the crux of this solution, binding properties to Java POJO
Object bean = Binder.get(context.getEnvironment()).bind(prefix, targetClass).orElse(null);
// if properties are not present at all return no match
if (bean == null) {
return ConditionOutcome.noMatch("Binding properties to target type resulted in null value.");
}
OptionalProperties props = (OptionalProperties) bean;
// execute method from OptionalProperties interface
// to check if condition should be matched or not
// can include any custom logic using property values in a type safe manner
if (props.isPresent()) {
return ConditionOutcome.match();
} else {
return ConditionOutcome.noMatch("Properties are not present.");
}
}
}
Now you should create your own configuration properties class implementing OptionalProperties interface.
#ConfigurationProperties("your.property.prefix")
#ConstructorBinding
public class YourConfigurationProperties implements OptionalProperties {
// Service is your POJO representing the name and version subproperties
private final List<Service> services;
#Override
public boolean isPresent() {
return services != null && services.stream().all(Service::isValid);
}
}
And then in Spring #Configuration class.
#Configuration
#ConditionalOnConfigurationProperties(prefix = "", targetClass = YourConfigurationProperties.class)
class AutoConfigureServices {
....
}
There are two downsides to this solution:
Property prefix must be specified in two locations: on #ConfigurationProperties annotation and on #ConditionalOnConfigurationProperties annotation. This can partially be alleviated by defining a public static final String PREFIX = "namespace" in your configuration properties POJO.
Property binding process is executed separately for each use of our custom conditional annotation and then once again to create the configuration properties bean itself. It happens only during app startup so it shouldn't be an issue but it still is an inefficiency.
You can leverage the org.springframework.boot.autoconfigure.condition.OnPropertyListCondition class. For example, given you want to check for the service property having at least one value:
class MyListCondition extends OnPropertyListCondition {
MyListCondition() {
super("service", () -> ConditionMessage.forCondition("service"));
}
}
#Configuration
#Condition(MyListCondition.class)
class AutoConfigureServices {
}
See the org.springframework.boot.autoconfigure.webservices.OnWsdlLocationsCondition used on org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration#wsdlDefinitionBeanFactoryPostProcessor for an example within Spring itself.

#Value at runtime

How do I access the #Value machinery dynamically at run-time?
I thought that Environment might be what I was looking for, but it
#Component
public class SpringConfiguration implements ConfigurationI {
#Autowired
private Provider<Environment> env;
#Override
public String get(String key) {
try {
return env.get().getRequiredProperty(key);
} catch (IllegalStateException e) {
return null;
}
}
}
Unfortunately, this does not access the values exposed by our PropertyPlaceholderConfigurer bean.
EDIT: To explain my use case: This is part of making a library with a lot of spring specific pieces (that a pile of older spring applications depend on) usable from newer Guice applications by switching Spring specific annotations for JSR 330 (javax.inject) ones. I was hoping to avoid rewriting all the PropertyPlaceholderConfigurer stuff across all our Spring applications, by providing a nice entrypoint like this. If there is another better way to do this (maybe with #Named?) then I am all ears.
EDIT2: This is a (cleaned up) example of what kind of PropertyPlaceholderConfigurer exists in the apps calling into this library.
#Bean
public PropertyPlaceholderConfigurer placeholderConfigurer() {
return new PropertyPlaceholderConfigurer() {
#Override
protected String resolvePlaceholder(String placeholder, Properties props) {
// Some code to parse and cleanup key here
String result = getPropertyFromLocalAppSpecificConfig(key);
if (result == null) {
result = super.resolvePlaceholder(placeholder, props);
}
// Some more random app specific logic for missing defaults
return result;
}
};
}
PropertyPlaceholder and friends do not put the properties in your Environment (mainly because of backward compatibility reasons). Instead they use Environment and its own internal Properties object gathered generally from property files from the classpath to resolve #Value properties. Thus the properties loaded from PropertyPlaceholder can not be fetched dynamically (ie no getProperty(String..)).
Some people create custom PropertyPlaceholder that store the properties publicly (through getter or whatever) but I think completely defeats Spring's new unified environment configuration handling.
What you really want is probably #PropertySource which still is pretty crappy since its not dynamic (since its an annotation you can't change where files get loaded from) but it will load properties into the Environment. I have been meaning to file issues with Spring Source about the confusion of this.
Anyway you can look at my solution here: Manually add a #PropertySource: Configuring Environment before context is refreshed
Basically you need to get hold of ConfigurableEnvironment and load your properties into it by creating PropertySources. The API for this is very powerful but not very intuitive. You can use ApplicationContextInitializers to get the Environment which has its own annoying issues (see link) or you can do what I do below.
public class ConfigResourcesEnvironment implements
ResourceLoaderAware, EnvironmentAware, BeanDefinitionRegistryPostProcessor, EnvironmentPropertiesMapSupplier {
private Environment environment;
private Map<String, String> environmentPropertiesMap;
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (environment instanceof ConfigurableEnvironment) {
ConfigurableEnvironment env = ((ConfigurableEnvironment) this.environment);
List<PropertySource> propertySources;
try {
propertySources = loadPropertySources(); //Your custom method for propertysources
} catch (IOException e) {
throw new RuntimeException(e);
}
//Spring prefers primacy ordering so we reverse the order of the sources... You may not need to do this.
reverse(propertySources);
for (PropertySource rp : propertySources) {
env.getPropertySources().addLast(rp);
}
environmentPropertiesMap = ImmutableMap.copyOf(environmentPropertiesToMap(env));
}
else {
environmentPropertiesMap = ImmutableMap.of();
}
}
public static Map<String,String> environmentPropertiesToMap(ConfigurableEnvironment e) {
Map<String, String> properties = newLinkedHashMap();
for (String n : propertyNames(e.getPropertySources())) {
String v = e.getProperty(n);
if (v != null)
properties.put(n, v);
}
return properties;
}
public static Iterable<String> propertyNames(PropertySources propertySources) {
LinkedHashSet<String> propertyNames = new LinkedHashSet<String>();
for (PropertySource<?> p : propertySources) {
if (p instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> e = (EnumerablePropertySource<?>) p;
propertyNames.addAll(asList(e.getPropertyNames()));
}
}
return propertyNames;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//NOOP
}
#Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
public Map<String, String> getEnvironmentPropertiesMap() {
return environmentPropertiesMap;
}
}
Once you have ConfigurableEnvironment loaded you can use the EnvironmentAware interface for things that need the Environment or create your own interface.
Here is a custom interface you can use for things that need dynamic properties (the above class implements it):
public interface EnvironmentPropertiesMapSupplier {
public Map<String, String> getEnvironmentPropertiesMap();
}

How customize spring validator?

I want to write custom validator with complex logic that depends on some properties that must be set in starup. It is possible to write component-scan tag in spring configuration file, but how to set other properties in class or spring validater are used as stateless classess?
public class CustomValidator implements Validator{
private Map<String,Integer> parameters;
public boolean supports(Class clazz) {
return ObjectToValidate.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
ObjectToValidate object = (ObjectToValidate) obj;
switch (parameters.get(object.getIntegerProperty())) {
case 1:
//validation algorithm 1;
break;
case 2:
//validation algorithm 2;
break;
//etc.
}
}
public void setParameters(Map<String,Integer> parameters){
this.parameters = parameters;
}
}
You should set up in your app context a org.springframework.validation.beanvalidation.LocalValidatorFactoryBean.
It will inject your validators with every #Autowired property they need. If you don't want to use autowiring, you should add them to your app context as beans.
Give a look here: http://static.springsource.org/spring/docs/3.0.0.RC3/reference/html/ch05s07.html
Everything is explained.
Stefano

Categories