Hide a method in springdoc-openapi based on a condition - spring-boot

We are working on a spring boot project which uses the springdoc-openapi library to generate the swagger document. We have a requirement where we need to hide a few of the APIs in a controller.
Spring boot does provide a way to hide/show a controller using the #ConditionalOnProperty tag. but spring boot does not have a way to hide/show a method based on property.
Does springdoc-openapi provide a way to filter the operation after scanning all controllers? or any other way to hide/show some APIs on swagger based on a property.

You can override springdoc'S OperationService#isHidden behavior like that:
package alex.swaggerhidden.service;
import org.springdoc.core.GenericParameterService;
import org.springdoc.core.OperationService;
import org.springdoc.core.PropertyResolverUtils;
import org.springdoc.core.RequestBodyService;
import org.springdoc.core.SecurityService;
import org.springdoc.core.providers.JavadocProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Optional;
#Component
#Primary
public class MyOperationService extends OperationService {
private final boolean exposeHidden;
public MyOperationService(GenericParameterService parameterBuilder, RequestBodyService requestBodyService, SecurityService securityParser, PropertyResolverUtils propertyResolverUtils, Optional<JavadocProvider> javadocProvider, #Value("${app.expose-hidden}") boolean exposeHidden) {
super(parameterBuilder, requestBodyService, securityParser, propertyResolverUtils, javadocProvider);
this.exposeHidden = exposeHidden;
}
#Override
public boolean isHidden(Method method) {
if (exposeHidden) {
return false;
}
return super.isHidden(method);
}
}
Now hidden behavior will be ignored if app.expose-hidden set to TRUE
app:
expose-hidden: true

Related

How to access matching rule details in Camunda DMN in a Spring Boot project?

My Spring Boot app connects to the Camunda BPMN & DMN. When a user clicks on Approve/Reject button in UI screen, a REST call triggers one BPMN workflow (in Camunda Spring Boot app) which internally triggers a DMN to find the output based on some business rules mapped in DMN input columns. We need to find the exact matching rules details like list of matching rules - input values, output values, matching rule ID, etc.
There are no inbuilt methods present in process engine and dmn engine and hence need to know if there is a way to manually configure such capabilities to the default process engine that comes as part of Spring Boot auto-configuration.
Camunda version:
7.13.0
In a Spring Boot app, if we have these dependencies in pom.xml - camunda-bpm-spring-boot-starter, camunda-bpm-spring-boot-starter-webapp, camunda-bpm-spring-boot-starter-rest, a default Process Engine will be auto-configured during the startup of the Spring Boot app. But this Process Engine does not by default enable the Rule details. So we configure a custom Process Engine plugin which will be applied on top of the default Process Engine in order to get the matching rule details every time DMN is hit.
import org.camunda.bpm.spring.boot.starter.annotation.EnableProcessApplication;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
#EnableProcessApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
Customize Process Engine:
import org.camunda.bpm.engine.impl.cfg.ProcessEnginePlugin;
import org.camunda.bpm.spring.boot.starter.configuration.Ordering;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
#Configuration
public class CamundaConfig {
#Bean
#Order(Ordering.DEFAULT_ORDER + 1)
public static ProcessEnginePlugin customProcessEnginePluginConfig() {
return new CustomProcessEnginePlugin();
}
}
Our CustomProcessEnginePlugin will extend AbstractCamundaConfiguration and override postInit() method. This postInit() method adds a DmnDecisionTableEvaluationListener. This listner triggers an event everytime DMN is hit with matching rule and has notify() method with event details which contains all the DMN details like dmnID and matching rules detail.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.camunda.bpm.dmn.engine.delegate.DmnDecisionTableEvaluationEvent;
import org.camunda.bpm.dmn.engine.delegate.DmnDecisionTableEvaluationListener;
import org.camunda.bpm.dmn.engine.delegate.DmnEvaluatedDecisionRule;
import org.camunda.bpm.dmn.engine.delegate.DmnEvaluatedInput;
import org.camunda.bpm.dmn.engine.impl.DefaultDmnEngineConfiguration;
import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.camunda.bpm.engine.variable.value.TypedValue;
import org.camunda.bpm.spring.boot.starter.configuration.Ordering;
import org.camunda.bpm.spring.boot.starter.configuration.impl.AbstractCamundaConfiguration;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
#Slf4j
#Component
#Order(Ordering.DEFAULT_ORDER + 1)
public class CustomProcessEnginePlugin extends AbstractCamundaConfiguration {
#Override
public void postInit(ProcessEngineConfigurationImpl processEngineConfig) {
DefaultDmnEngineConfiguration dmnEngineConfig = processEngineConfig.getDmnEngineConfiguration();
dmnEngineConfig.customPostDecisionTableEvaluationListeners(Collections.singletonList(new DmnDecisionTableEvaluationListener(){
#Override
public void notify(DmnDecisionTableEvaluationEvent event) {
String dmnID = event.getDecisionTable().getKey();
Map<String, TypedValue> dmnInput = event.getInputs().stream().collect(Collectors.toMap(DmnEvaluatedInput::getName, DmnEvaluatedInput::getValue));
List<DmnEvaluatedDecisionRule> matchingRuleList = new ArrayList<>();
matchingRuleList = event.getMatchingRules();
log.info("DMN ID = {}", dmnID);
log.info("DMN Input = {}", dmnInput);
if(null != matchingRuleList) {
log.info("DMN Matched Rules = {}", matchingRuleList.size());
if(!matchingRuleList.isEmpty()) {
for(DmnEvaluatedDecisionRule rule : matchingRuleList) {
log.info("DMN Matching Rule ID = {}", rule.getId());
log.info("DMN Output = {}", rule.getOutputEntries());
}
}
else {
log.info("DMN Output = No matching rule found");
}
}
log.info("************************************************************************************************");
}
}));
dmnEngineConfig.buildEngine();
processEngineConfig.setDmnEngineConfiguration(dmnEngineConfig);
}
}
There is another way to get the matching rule details - by implementing ProcessEnginePlugin and overriding postProcessEngineBuild() and preInit() methods instead of extending AbstractCamundaConfiguration and overriding postInit() method as above.

Spring loading and using bean from module

I have divided my project into 3 modules- client, server, and core. In the core module, I defined the required settings for the sentry. I would like to use those beans in client and server modules so that I don't have to redefine them for each module.
Is it possible? If so please give example.
My current sentry setup in the core module.
org/project/core/configurations/SentryConfiguration.java
package org.project.core.configurations;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
#Configuration
public class SentryConfiguration {
/*
This class is constructed according official sentry documentation.
URL: https://docs.sentry.io/platforms/java/guides/spring/
*/
#Bean
public HandlerExceptionResolver sentryExceptionResolver() {
return new io.sentry.spring.SentryExceptionResolver();
}
#Bean
public ServletContextInitializer sentryServletContextInitializer() {
return new io.sentry.spring.SentryServletContextInitializer();
}
}

Is it possible to set the default date format in JSON-B (Yasson) globally, instead of adding an annotation on every property?

I have using Jersey so far and I am doing my first implementation with JSON-B.
I am using Payara, so I working with Jersey and Yasson. I had an issue, because the serialized dates would always contain the "[UTC]" suffix.
I have managed to use an annotation on my date property, in my DTO. But I would like to configure that globally (in the JAX-RS application config?), instead of repeating myself on every date property. Is that possible? I haven't found anything so far...
Side question: I assume that it is possible to get rid of this "[UTC]" suffix, since it breaks all clients trying to parse the date. Any idea?
Thanks to this Github issue, I was able to solve my problem. Here is what I ended up writing in my code:
JSONConfigurator.java:
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.bind.JsonbConfig;
import javax.json.bind.config.PropertyNamingStrategy;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
#Provider
public class JSONConfigurator implements ContextResolver<Jsonb> {
#Override
public Jsonb getContext(Class<?> type) {
JsonbConfig config = getJsonbConfig();
return JsonbBuilder
.newBuilder()
.withConfig(config)
.build();
}
private JsonbConfig getJsonbConfig() {
return new JsonbConfig()
.withDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", null);
}
}
And:
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;
#ApplicationPath("/api")
public class ApplicationConfig extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new HashSet<Class<?>>();
addRestResourceClasses(resources);
resources.add(JSONConfigurator.class);
return resources;
}
private void addRestResourceClasses(Set<Class<?>> resources) {
...
}
}

Capture org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter.onMessage() invocation

I'm using AspectJ and AOP in a Spring-boot project in order create an external library to log some activities.
Although I have configured this pointcut:
#Pointcut("call(void org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter.onMessage(Message,Channel))")
private void getEventOnMessage(){}
the aspect
#Before(value="getEventOnMessage()")
public void getEventOnMessage(JoinPoint joinPoint){
System.out.println("VOILA'");
}
is not triggered.
Details:
package com.tim.sdp.timLogging.Aspects.handler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
#Configuration
#EnableAspectJAutoProxy
#ComponentScan(basePackages="org.springframework.amqp.rabbit.listener.adapter")
public class AppConfig {
#Bean()
public AspectForOnMessage myAspect() {
return new AspectForOnMessage();
}
}
Aspect class implementation:
package com.tim.sdp.timLogging.Aspects.handler;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
#Component
#Aspect
public class AspectForOnMessage {
#Pointcut("call(void org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter.onMessage(Message,Channel))")
private void getEventOnMessage(){}
#Before(value="getEventOnMessage()")
public void getEventOnMessage(JoinPoint joinPoint){
System.out.println("VOILA'");
}
}
Might you help me, please? It's the only event I can not capture.
In this forum you can find another person with the same problem:
Spring forum link
Thank you in advance.
Oh, a classical one!
As documented here, call() is not supported in proxy-based Spring AOP which you have configured in your application via #EnableAspectJAutoProxy. You need to switch from "AOP lite" to the full power of AspectJ as described there or stick with pointcuts really supported in Spring AOP.

Spring Boot & Hibernate Validation's ConstraintMappingContributor

The hibernate validations documentation describes how to create ConstraintMappingContributors here.
It states:
You then need to specify the fully-qualified class name of the
contributor implementation in META-INF/validation.xml, using the
property key hibernate.validator.constraint_mapping_contributors. You
can specify several contributors by separating them with a comma.
Given I have many of these, what would be the most appropriate way to auto-discover these i.e. via #Component and add them dynamically at runtime to the ConstrainMappingConfiguration during Spring Boot startup.
For example.. if a developer creates a new ConstraintMappingContributor, it should be picked up and added automatically when spring boot starts, requiring no other file changes.
This is what I came up with, seems to be working for me.
package...
import org.hibernate.validator.spi.cfg.ConstraintMappingContributor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
#Configuration
public class ValidationConfiguration {
private final List<ConstraintMappingContributor> contributors;
public ValidationConfiguration(Optional<List<ConstraintMappingContributor>> contributors) {
this.contributors = contributors.orElseGet(ArrayList::new);
}
#Bean
public LocalValidatorFactoryBean validatorFactory() {
return new ValidatorFactoryBean(this.contributors);
}
}
package...
import org.hibernate.validator.HibernateValidatorConfiguration;
import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping;
import org.hibernate.validator.spi.cfg.ConstraintMappingContributor;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import javax.validation.Configuration;
import java.util.List;
public class ValidatorFactoryBean extends LocalValidatorFactoryBean {
private final List<ConstraintMappingContributor> contributors;
ValidatorFactoryBean(List<ConstraintMappingContributor> contributors) {
this.contributors = contributors;
}
#Override
protected void postProcessConfiguration(Configuration<?> cfg) {
if (cfg instanceof HibernateValidatorConfiguration) {
HibernateValidatorConfiguration configuration = (HibernateValidatorConfiguration) cfg;
this.contributors.forEach(contributor -> contributor.createConstraintMappings(() -> {
DefaultConstraintMapping mapping = new DefaultConstraintMapping();
configuration.addMapping(mapping);
return mapping;
}));
}
}
}
I invoke it like this...
if(SpringValidatorAdapter.class.isInstance(this.validatorFactory)){
SpringValidatorAdapter.class.cast(this.validatorFactory).validate(entity, errors);
}

Resources