How do I add things to the /info endpoint in spring boot programmatically? - spring-boot

How do I add things to the /info endpoint in Spring Boot programmatically? The documentation states that this is possible for the /health endpoint through the use of HealthIndicator interface. Is there something for the /info endpoint as well?
I would like to add operating system name and version and other runtime info there.

In Spring Boot 1.4, you are able to declare InfoContributer beans to make this a whole lot easier:
#Component
public class ExampleInfoContributor implements InfoContributor {
#Override
public void contribute(Info.Builder builder) {
builder.withDetail("example",
Collections.singletonMap("key", "value"));
}
}
See http://docs.spring.io/spring-boot/docs/1.4.0.RELEASE/reference/htmlsingle/#production-ready-application-info-custom for more info.

The accepted answer actually clobbers the InfoEndpoint and does not add to it.
One way I found to add to the info is, in a #Configuration class, add an #Autowired method that adds extra properties following the info.* conventions to the environment. Then InfoEndpoint will pick them up when its invoked.
You can do something like the following:
#Autowired
public void setInfoProperties(ConfigurableEnvironment env) {
/* These properties will show up in the Spring Boot Actuator /info endpoint */
Properties props = new Properties();
props.put("info.timeZone", ZoneId.systemDefault().toString());
env.getPropertySources().addFirst(new PropertiesPropertySource("extra-info-props", props));
}

One way to do what you want (in the event that you have totally custom properties you need to display) is to declare a bean of type InfoEndpoint which will override the default.
#Bean
public InfoEndpoint infoEndpoint() {
final LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>();
map.put("test", "value"); //put whatever other values you need here
return new InfoEndpoint(map);
}
As you can see from the code above, the map can contain whatever info you need.
In the event that the data you want to show can be retrieved by the environment and is not custom, you do not need to override the InfoEndpoint bean, but you can simply add properties to the properties file with a prefix of info. One example where the OS name is evaluated is the following:
info.os = ${os.name}
In the code above, Spring Boot will evaluate the right-hand expression before returning the property in the /info endpoint.
A final note is that there is a ton of environment information that is already available in the /env endpoint
Update
As pointed out by #shabinjo, in newer Spring Boot versions there is no InfoEndpoint constructor that accepts a map.
You can however use the following snippet:
#Bean
public InfoEndpoint infoEndpoint() {
final Map<String, Object> map = new LinkedHashMap<String, Object>();
map.put("test", "value"); //put whatever other values you need here
return new InfoEndpoint(new MapInfoContributor(map));
}
The code above will completely override the default info that would end-up in /info.
To overcome this issue one could add the following bean
#Bean
public MapInfoContributor mapInfoContributor() {
return new MapInfoContributor(new HashMap<String, Object>() {{
put("test", "value");
}});
}

It should be possible to add a custom PropertySource inside an ApplicationListener to add custom info.* properties to the environment (see this answer for an example: How to Override Spring-boot application.properties programmatically)

Related

intercept request start and end in Vaadin 14 (Flow) with Spring Boot

I'm using vaadin-spring-boot-starter for integration of Vaadin Framework 14 and Spring Boot.
I would like to override the requestStart and requestEnd methods of the SpringServlet class to do the following things:
put stuff such as the current route / view path and current user ID into the SLF4J MDC in order to include it in each logging statement
log the duration of the request
In Vaadin 8 there was a SpringVaadinServlet class which I could replace by simply annotating my custom subclass with #SpringComponent("vaadinServlet").
This approach no longer works. The vaadin-spring integration contains SpringBootConfiguration which contains a direct call to the SpringServlet constructor:
#Bean
public ServletRegistrationBean<SpringServlet> servletRegistrationBean() {
String mapping = configurationProperties.getUrlMapping();
Map<String, String> initParameters = new HashMap<>();
boolean rootMapping = RootMappedCondition.isRootMapping(mapping);
if (rootMapping) {
mapping = VaadinServletConfiguration.VAADIN_SERVLET_MAPPING;
initParameters.put(Constants.SERVLET_PARAMETER_PUSH_URL,
VaadinMVCWebAppInitializer
.makeContextRelative(mapping.replace("*", "")));
}
ServletRegistrationBean<SpringServlet> registration = new ServletRegistrationBean<>(
new SpringServlet(context, rootMapping), mapping); // <-- HERE
registration.setInitParameters(initParameters);
registration.setAsyncSupported(configurationProperties.isAsyncSupported());
registration.setName(
ClassUtils.getShortNameAsProperty(SpringServlet.class));
return registration;
}
They should use a conditional bean here so we could replace it, but unfortunately they're not.
Just adding a custom ServletRegistrationBean with a copy of the above code (but the constructor call substituted with my own) doesn't work, even with #Primary.
So is there a better way to do what I want than to exclude the whole vaadin-spring autoconfiguration and copy everything in my own configuration bean? It works but I have to check if everything's still OK after each vaadin-spring upgrade.
You could add a VaadinServiceInitListener through which you can add a custom request handler. Alternatively you could use a Filter.

Reading applicatiion.properties once in a singleton class

I have a singleton configuration class where I want to store all the properties for our web application.
How do we read in the application.properies file like any other properties file without using annotations?
What is the the fully qualified filename for application.properies i.e. /application.properies?
We only want to read application.properties once.
Spring boot already reads all the properties stored in application.properties and much more, read Externalized Configuration documentation.
If you want to map one property named server.port you can just use #Value("${server.port}") Integer port.
If you want to access to all the properties loaded by Spring Boot, you can use the Environment object and access to all loaded PropertySources and retrieve all values from each property source.
In this this answer shows how. However, to avoid losing the precedence order of loaded properties, you have to reverse the property source list. Here you can find the code to load all the properties without losing the spring precedence order:
#Configuration
public class AppConfiguration {
#Autowired
Environment env;
public void loadProperties() {
Map<String, Object> map = new HashMap();
for (Iterator it = ((AbstractEnvironment) env).getPropertySources().iterator().reverse(); it.hasNext(); ) {
PropertySource propertySource = (PropertySource) it.next();
if (propertySource instanceof MapPropertySource) {
map.putAll(((MapPropertySource) propertySource).getSource());
}
}
}
}

Preventing Spring Boot from creating nested map from dot-separated key in application.yml?

I have a problem with Spring Boot creating a nested map from a dot-separated key. It's essentially the same problem that is described here, but the solution suggested there doesn't work for me. I'm using Spring Boot 1.5.3.RELEASE in case it matters. My applications.yml file contains this:
props:
webdriver.chrome.driver: chromedriver
My config class:
#Configuration
#EnableConfigurationProperties
public class SpringConfig {
private Map<String, String> props = new HashMap<>();
#ConfigurationProperties(prefix = "props")
public void setProps(Map<String, String> props) {
this.props = props;
}
#ConfigurationProperties(prefix = "props")
#Bean(destroyMethod="", name = "props")
public Map<String, String> getProps() {
return props;
}
}
Unfortunately, after Spring Boot processes the YAML file, the dot separated key gets split up into sub-maps. The result from callig getProps() and printing the result to System.out looks like this:
{webdriver={chrome={driver=chromedriver}}}
I've tried changing the type of the props field to Properties, Map<String, Object> etc, but nothing seems to make any difference.
I haven't found any way of manipulating the parsing behavior to accomplish what I want. Any help is much appreciated. I've spent so much time on this, that I'll go blind if I look at the code any further.
Try using YamlMapFactoryBean this will load YAML as MAP.
#Bean
public YamlMapFactoryBean yamlFactory() {
YamlMapFactoryBean factory = new YamlMapFactoryBean();
factory.setResources(resource());
return factory;
}
public Resource resource() {
return new ClassPathResource("application.yml");
}
public Map<String, String> getProps() {
props = yamlFactory().getObject();
return props;
}
The output looks
props{webdriver.chrome.driver=chromedriver}
After much experimenting, this seemed to work:
#Configuration
#EnableAutoConfiguration
#EnableConfigurationProperties
#ConfigurationProperties
public class SpringConfig {
private Properties info = new Properties();
public Properties getProps() {
return info;
}
}
}
But I had to put single quotes around the YAML entry, otherwise Spring Boot would make the property nested:
props:
'webdriver.chrome.driver': chromedriver
'foo.bar.baz': foobarbaz
A couple of things I noticed. The getter for the Properties (getProps() in this case) must be declared public, and it has to match the property key that you're trying to bind in the YAML. I.e. since the key is 'props', the getter has to be called getProps(). I guess it's logical, and documented somewhere, but that had slipped me by somehow. I thought by using the prefix="foobar" on the #ConfigurationProperties annotation, that wasn't the case, but it seems to be. I guess I should RTFM ;-)

Spring-boot ConditionalOnProperty with map-based properties

My spring-boot yaml properties look like this:
service:
mycomponent:
foo:
url: http://foo
bar:
url: http://bar
This results in the following properties being set in the Spring environment:
service.mycomponent.foo.url: http://foo
service.mycomponent.bar.url: http://bar
I'd like to define a 'mycomponent' bean if there are any properties that match service.mycomponent.[a-z]*.url. Is this possible using #ConditionalOnExpression or some other type of #Conditional?
I realize I can work around this by either adding a property such as service.mycomponent.enabled: true that could be used with #ConditionalOnProperty but I'd rather avoid that if possible.
Here's the solution I ended up taking:
Create a custom Condition which searches for any properties with a certain prefix. The RelaxedPropertyResolver has the convenient getSubProperties() method. Alternative options I found were cumbersome to iterate through the PropertySource instances.
public class MyComponentCondition extends SpringBootCondition {
#Override
public ConditionOutcome getMatchOutcome(final ConditionContext context,
final AnnotatedTypeMetadata metadata) {
final RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(context.getEnvironment());
final Map<String, Object> properties = resolver.getSubProperties("service.mycomponent.");
return new ConditionOutcome(!properties.isEmpty(), "My Component");
}
}
Use that condition when setting up the bean:
#Conditional(MyComponentCondition.class)
#Bean
public MyComponent myComponent() {
return new MyComponent();
}
I'm still curious if the same thing could be done with #ConditionalOnExpression directly.

spring-security global-method-security protect-pointcut with #EnableGlobalMethodSecurity

How does one port from
<sec:global-method-security secured-annotations="disabled">
<sec:protect-pointcut expression='execution(* x.y.z.end*(..))' access='...' />
to spring java-config
#EnableGlobalMethodSecurity
#Configuration
public class MyConfiguration extends WebSecurityConfigurerAdapter {
?
There is a simmilar question here http://forum.spring.io/forum/spring-projects/security/726615-protect-pointcut-in-java-configuration
There's a workaround for it. The security points information is kept in MethodSecurityMetadataSource implementations (which are then used by MethodInterceptor) so we have to create an additional MethodSecurityMetadataSource. As mentioned in the spring forum post the xml pointcut configuration is kept in MapBasedMethodSecurityMetadataSource and processed by ProtectPointcutPostProcessor. we also need an instance of ProtectPointcutPostProcessor. Unfortunately this class is final and package-private so there are 2 options:
create your own class and copy/paste the whole content of the original one (that's what I did)
change the class modifiers with reflection and create an instance of the original one (haven't done that so no idea if it would work fine)
then create the following beans in your context:
#Bean
public Map<String, List<ConfigAttribute>> protectPointcutMap() {
Map<String, List<ConfigAttribute>> map = new HashMap<>();
// all the necessary rules go here
map.put("execution(* your.package.service.*Service.*(..))", SecurityConfig.createList("ROLE_A", "ROLE_B"));
return map;
}
#Bean
public MethodSecurityMetadataSource mappedMethodSecurityMetadataSource() {
// the key is not to provide the above map here. this class will be populated later by ProtectPointcutPostProcessor
return new MapBasedMethodSecurityMetadataSource();
}
// it's either the original spring bean created with reflection or your own copy of it
#Bean
public ProtectPointcutPostProcessor pointcutProcessor() {
ProtectPointcutPostProcessor pointcutProcessor = new ProtectPointcutPostProcessor((MapBasedMethodSecurityMetadataSource) mappedMethodSecurityMetadataSource());
pointcutProcessor.setPointcutMap(protectPointcutMap());
return pointcutProcessor;
}
we've created the necessary beans, now we have to tell spring to use them. I'm assuming you're extending GlobalMethodSecurityConfiguration. by default it creates DelegatingMethodSecurityMetadataSource which contains a list of other MethodSecurityMetadataSources. Depending on what you want to achieve you have following options:
if you want to keep all the other MethodSecurityMetadataSources (like the ones for parsing the #Secured annotations) you can extend the list in the delegating metadata source by overriding the following method:
#Override
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return mappedMethodSecurityMetadataSource();
}
it would inject it on first place in the list though which may cause some problems.
if you want to keep the other sources but want yours to be the last in the list then override the following method:
#Override
public MethodSecurityMetadataSource methodSecurityMetadataSource() {
DelegatingMethodSecurityMetadataSource metadataSource = (DelegatingMethodSecurityMetadataSource) super.methodSecurityMetadataSource();
metadataSource.getMethodSecurityMetadataSources().add(mappedMethodSecurityMetadataSource());
return metadataSource;
}
if you want your source to be the only one (you don't want to use #Secured or any other annotations) then you can override the same method, just with different content
#Override
public MethodSecurityMetadataSource methodSecurityMetadataSource() {
return mappedMethodSecurityMetadataSource();
}
that's it! I hope it will help
I followed #marhewa comments and have been able to use the Spring version of class ProtectPointcutPostProcessor by defining the following bean
/**
* Needed to use reflection because I couldn't find a way to instantiate a
* ProtectPointcutPostProcessor via a BeanFactory or ApplicationContext. This bean will process
* the AspectJ pointcut defined in the map; check all beans created by Spring; store the matches
* in the MapBasedMethodSecurityMetadataSource bean so Spring can use it during its checks
*
* #return
* #throws Exception
*/
#Bean(name = "protectPointcutPostProcessor")
Object protectPointcutPostProcessor() throws Exception {
Class<?> clazz =
Class.forName("org.springframework.security.config.method.ProtectPointcutPostProcessor");
Constructor<?> declaredConstructor =
clazz.getDeclaredConstructor(MapBasedMethodSecurityMetadataSource.class);
declaredConstructor.setAccessible(true);
Object instance = declaredConstructor.newInstance(pointcutMethodMetadataSource());
Method setPointcutMap = instance.getClass().getMethod("setPointcutMap", Map.class);
setPointcutMap.setAccessible(true);
setPointcutMap.invoke(instance, pointcuts());
return instance;
}
This way I don't need to duplicate the code of this Spring class.
Cheers

Resources