How to call caffeine stats in a seperate class - spring

Is there a way to call caffeine cache in a seperate class implementation.
I wrote a Cache configurtion :
`#Configuration
#EnableCaching(mode = AdviceMode.ASPECTJ)
public class CacheConfiguration {
#Bean
public Caffeine caffeineConfig() {
return Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).recordStats();
}
#Bean
public CacheManager cacheManager(Caffeine caffeine) {
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
caffeineCacheManager.getCache("addresses");
caffeineCacheManager.setCaffeine(caffeine);
return caffeineCacheManager;
}
}`
I want to log statistics in another class so I did this:
public class RequestLoggingFilter extends AbstractRequestLoggingFilter {
#Autowired
public Caffeine caffeineConfig;
#Override
protected void afterRequest(HttpServletRequest request, String message) {
// Payload could be logged only after request is processed (it uses a
ContentCachingRequestWrapper internally)
this.logger.info(message);
this.logger.info(this.caffeineConfig.build().stats().hitCount());
}
}
From my opinion the .build() should be called only one time. Is there another way or best practice to do it?

Related

Is it possible to define a custom rest template?

I'm trying to define a common bean to be used for all my application so to add inside a logger and other logic. My idea would be:
public class MyRestTemplate extends RestTemplate{
Then:
#Configuration
public class RestTemplateConfig {
#Bean
public MyRestTemplate myRestTemplate(RestTemplateBuilder builder){
return (MyRestTemplate) builder.build(); //throws classcast exception!
}
}
What am I doing wrong? Is there another way? I want to be sure that people will have to use my customized class.
If you want some customizations in your restTemplate you could define a class that implements RestTemplateCustomizer and add a custom interceptor to it.
public class CustomRestTemplateCustomizer implements RestTemplateCustomizer {
#Override
public void customize(RestTemplate restTemplate) {
restTemplate.getInterceptors().add(new CustomClientHttpRequestInterceptor());
}
}
Then you have to define that custom interceptor for all the requests going out of this restTemplate with
public class CustomClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
// This is where you can do a lot of thing with this request like logging
return execution.execute(request, body);
}
}
And finally, just define a bean for the custom restTemplate you have written
#Bean
public CustomRestTemplateCustomizer customRestTemplateCustomizer() {
return new CustomRestTemplateCustomizer();
}
builder.build() returns a RestTemplate, not a MyRestTemplate.
If you change your code as shown below you would create a bean named myRestTemplate. Spring use the name of the method as bean name if you don't override it in the #Bean annotation.
#Configuration
public class RestTemplateConfig {
#Bean
public RestTemplate myRestTemplate(RestTemplateBuilder builder){
return builder.build(); //throws classcast exception!
}
}
Please also see https://docs.spring.io/spring-boot/docs/1.5.x/reference/html/boot-features-restclient.html

Spring Boot - Store current user in global variable and initialise from API call when #service bean is created

I am creating a microservice architectured project with Zuul as gateway. I have all authentication handled in a service called common-service. I have exposed a API from common-service to return current logged in user. This is working fine.
Now, I have another microservice called inventory. In service class of inventory, I want to use current loggedin username in multiple methods. So, I am making a webclient call to common-service and getting current username. This is working fine but I am making a webclient API call to common service everytime I require username. Example - if I add a new entry, doing API call, then on update again API call etc. this seems not to be an optimised way
so problem is - I want to make this API call at global level. i.e. whenever my service bean is autowired, this API call should be made and username should be store somewhere which I can use across methods in my service call.
I tried #PostConstruct and #SessionAttributes but not able to get exact problem solved.
Can somebody help me with best suited solution or concept for handling this issue.
Below are code snippets
public class LeadService
{
#Autowired
WebClient.Builder webClientBuilder;
#Autowired
UserDetailsService userDetailsService;
//more autowiring
private void setLeadFields(Lead lead, #Valid LeadCreateData payload,String type)
{
//some logic
if(type.equalsIgnoreCase("create"))
{
lead.setAsigneeId(userDetailsService.getCurrentUser().getId());
lead.setCreatorId(userDetailsService.getCurrentUser().getId());
}
else if(type.equalsIgnoreCase("update"))
{
//some logic
}
}
private StatusEnum setLeadStatus(Lead lead, StatusEnum status,String string)
{
LeadStatus lstatus=null;
switch(string)
{
case "create":
lstatus = new LeadStatus(lead.getLeadId(),status,userDetailsService.getCurrentUser().getId(),userDetailsService.getCurrentUser().getId());
lsRepo.save(lstatus);
break;
case "udpate":
lstatus= lsRepo.FindLeadStatusByLeadID(lead.getLeadId()).get(0);
if(!lstatus.getStatus().equals(lstatus))
{
lstatus = new LeadStatus(lead.getLeadId(),status,userDetailsService.getCurrentUser().getId(),userDetailsService.getCurrentUser().getId());
lsRepo.save(lstatus);
}
break;
}
return lstatus.getStatus();
}
private Address setAddress(#Valid LeadCreateData payload,Address address)
{
//some setters
address.setCreator(userDetailsService.getCurrentUser().getId());
return aRepo.save(address);
}
As you can see, I am using userDetailsService.getCurrentUser().getId() in many places. I am getting this id from below autowired method. But my one API call is required everytime I need this id.
#Service
public class UserDetailsService
{
#Autowired
WebClient.Builder webClientBuilder;
#Autowired
HttpServletRequest request;
#Value("${common.serverurl}")
private String reqUrl;
public UserReturnData getCurrentUser()
{
UserReturnData userDetails = webClientBuilder.build()
.get()
.uri(reqUrl+"user/me")
.header("Authorization", request.getHeader("Authorization"))
.retrieve()
.bodyToMono(UserReturnData.class)
.block();
return userDetails;
}
}
I want a optimal way where I can call this API method to get current user only once. and I can use it throughout my #service class.
Create OncePerPrequestFilter or GenericFilterBean which has your UserDetailsService autowired.
And also you want to create something similar to RequestContextHolder or SecurityContextHolder which can hold your UserReturnData in a ThreadLocal variable. Look at those two spring classes to get idea but yours can be much simpler. Lets call it UserReturnDataContextHolder.
In the filter, you created in step1, when the request comes in populate it and when the response is leaving, clear it.
Now you can access it anywhere in the service via UserReturnDataContextHolder.getUserReturnData() and you are not making multiple calls either
Edit: The section below is contributed by Sridhar Patnaik as reference -
Below code to get it working
Added a class to store currentuserid
public class CurrentUser
{
private Long currentUserId;
//getter setter
}
Added a current user filter to intercept request and fetch current user.
public class CurrentUserFilter implements Filter
{
#Autowired
private CurrentUser currentUser;
#Autowired
UserDetailsService UserDetailsService;
#Override
public void init(FilterConfig arg0) throws ServletException {
// NOOP
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
try
{
this.currentUser.setCurrentUserId(UserDetailsService.getCurrentUser().getId());
chain.doFilter(servletRequest, servletResponse);
}
finally
{
this.currentUser.clear();
}
}
#Override
public void destroy() {
// NOOP
}
}
Added required AppConfig
#Configuration
public class AppConfig
{
#Bean
public Filter currentUserFilter() {
return new CurrentUserFilter();
}
#Bean
public FilterRegistrationBean tenantFilterRegistration() {
FilterRegistrationBean result = new FilterRegistrationBean();
result.setFilter(this.currentUserFilter());
result.setUrlPatterns(Lists.newArrayList("/*"));
result.setName("Tenant Store Filter");
result.setOrder(1);
return result;
}
#Bean(destroyMethod = "destroy")
public ThreadLocalTargetSource threadLocalTenantStore() {
ThreadLocalTargetSource result = new ThreadLocalTargetSource();
result.setTargetBeanName("tenantStore");
return result;
}
#Primary
#Bean(name = "proxiedThreadLocalTargetSource")
public ProxyFactoryBean proxiedThreadLocalTargetSource(ThreadLocalTargetSource threadLocalTargetSource) {
ProxyFactoryBean result = new ProxyFactoryBean();
result.setTargetSource(threadLocalTargetSource);
return result;
}
#Bean(name = "tenantStore")
#Scope(scopeName = "prototype")
public CurrentUser tenantStore() {
return new CurrentUser();
}
}
And then autowired CurrentUser to my existing service class.
{..
#Autowired
CurrentUser currentUser;
...
private void setLeadFields(Lead lead, #Valid LeadCreateData payload,String type)
{
//some logic
if(type.equalsIgnoreCase("create"))
{
lead.setAsigneeId(currentUser.getCurrentUserId());
lead.setCreatorId(currentUser.getCurrentUserId());
lead.setAddress(setAddress(payload, new Address()));
}
else if(type.equalsIgnoreCase("update"))
{
lead.setAsigneeId(userDetailsService.getUserFromId(payload.getAssigneeId()).getId());
lead.setAddress(setAddress(payload,lead.getAddress()));
}
}

How do I spring cloud gateway custom filter e2e test?

I have implemented custom GatewayFilterFactory filter. But I don't know how to test this filter with e2e setup.
I have referenced official spring-cloud-gateway AddRequestHeaderGatewayFilterFactoryTests test case code.
This is my custom filter code:
#Component
public class MyCustomFilter implements GatewayFilterFactory<MyCustomFilter.Config>, Ordered {
#Override
public GatewayFilter apply(Config config) {
return new OrderedGatewayFilter((this::filter), getOrder());
}
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/* do some filtering */
}
#Override
public int getOrder() {
return 1000;
}
#Override
public Config newConfig() {
return new Config(MyCustomFilter.class.getSimpleName());
}
public static getConfig() {
return
}
#Getter
#Setter
public static class Config {
private String name;
Config(String name) {
this.name = name;
}
}
}
And this is my test code:
BaseWebClientTests class look exactly the same as official BaseWebClientTests class code
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = RANDOM_PORT)
#DirtiesContext
#ActiveProfiles("my-custom-filter")
public class MyCustomFilterTests extends BaseWebClientTests {
#LocalServerPort
protected int port = 0;
protected WebTestClient testClient;
protected WebClient webClient;
protected String baseUri;
#Before
public void setup() throws Exception {
setup(new ReactorClientHttpConnector(), "http://localhost:" + port);
}
protected void setup(ClientHttpConnector httpConnector, String baseUri) {
this.baseUri = baseUri;
this.webClient = WebClient.builder().clientConnector(httpConnector)
.baseUrl(this.baseUri).build();
this.testClient = WebTestClient
.bindToServer(httpConnector)
.baseUrl(this.baseUri)
.build();
}
#Test
public void shouldFailByFilterTests() {
/* This test should be failed but success :( */
testClient.get().uri("/api/path")
.exchange().expectBody(Map.class).consumeWith(result -> {
/* do assertion */
});
}
#EnableAutoConfiguration
#SpringBootConfiguration
#Import(DefaultTestConfig.class)
public static class TestConfig {
#Value("${test.uri}")
String uri;
#Bean
public MyCustomFilter myCustomFilter() {
return new MyCustomFilter();
}
#Bean
public RouteLocator testRouteLocator(RouteLocatorBuilder builder, MyCustomFilter myCustomFilter) {
return builder.routes().route("my_custom_filter",
r -> r.path("/api/path")
.filters(f -> f.filter(myCustomFilter.apply(new MyCustomFilter.Config("STRING"))))
.uri(uri))
.build();
}
}
}
Lastly Target controller looks like this:
#RestController
#RequestMapping("/api/path")
public class HttpBinCompatibleController {
#GetMapping("/")
public Mono<BodyData> identity() {
return Mono.just(new BodyData("api success"));
}
#NoArgsConstructor
#AllArgsConstructor
#Getter
static class BodyData {
private String message;
}
}
What I understand how this filter factory test code works is that
custom filter: custom filter is setup inside TestConfig class testRouteLocator method
target controller: target controller is defined as HttpBinCompatibleController class
testClient sends the request, and custom should do some filtering, then target controller should receive the request from testClient.
What I expect from this shouldFailByFilterTests TC is that before request from testClient is sent to target controller, that request should be rejected by MyCustomFilter. But the request is sent to the target controller.
I think the request from testClient is not proxied by testRouteLocator but I'm not sure
Question
What is the cause of this problem?
Is there another way to test my own custom filter?
This problem was related to the version incompatibility between Spring Boot and Spring Cloud.
I was using Spring Boot version 2.1.7 and Spring Cloud version Greenwich.SR2.
Then I found this 'Release train Spring Boot compatibility' table on this link
Before I've noticed version incompatibility, for using #Configuration(proxyBeanMethods = false) feature, upgraded Spring Boot version to 2.2.x.
The solution is using 2.1.x branch BaseWebClientTests class.

Custom AbstractEndpoint listening to "/" (root)

I've implemented a starter that configures Swagger the way I like. In addition, I'd like to redirect every call to the app's root URL (e.g. localhost:8080) to /swagger-ui.html.
Therefore, I added an own AbstractEndpoint which is instantiated in the #Configuration class as follows:
#Configuration
#Profile("swagger")
#EnableSwagger2
public class SwaggerConfig {
...
#Bean
public RootEndpoint rootEndpoint() {
return new RootEndpoint();
}
#Bean
#ConditionalOnBean(RootEndpoint.class)
#ConditionalOnEnabledEndpoint("root")
public RootMvcEndpoint rootMvcEndpoint(RootEndpoint rootEndpoint) {
return new RootMvcEndpoint(rootEndpoint);
}
}
The respective classes look like this:
public class RootEndpoint extends AbstractEndpoint<String> {
public RootEndpoint() {
super("root");
}
#Override
public String invoke() {
return ""; // real calls shall be handled by RootMvcEndpoint
}
}
and
public class RootMvcEndpoint extends EndpointMvcAdapter {
public RootMvcEndpoint(RootEndpoint delegate) {
super(delegate);
}
#RequestMapping(method = {RequestMethod.GET}, produces = { "*/*" })
public void redirect(HttpServletResponse httpServletResponse) throws IOException {
httpServletResponse.sendRedirect("/swagger-ui.html");
}
}
As stated in public RootEndpoint(), the custom Endpoint is bound to /root. Unfortunately, I can't specify super(""); or super("/"); as those values throw an exception (Id must only contains letters, numbers and '_').
How can I achieve having a custom Endpoint listening to the root URL in a starter using #Configuration files to instantiate beans?
I solved it with an easier approach by adding a WebMvcConfigurerAdapter bean in the #Configuration:
#Bean
public WebMvcConfigurerAdapter redirectToSwagger() {
return new WebMvcConfigurerAdapter() {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("").setViewName("redirect:/swagger-ui.html");
}
};
}

How to write custom interceptor for spring cache(#cachable)

I am caching data using spring cache. Now i want to encrypt few data before writing into cache and decrypt data while reading. So is there any way i can write custom interceptor/aop for #cachable annotation
Instead of using AOP you can simply use a decorator for your Cache and CacheResolver.
public class EncodingCacheResolver implements CacheResolver {
private final CacheResolver delegate;
public EncodingCacheResolver(CacheResolver delegate) {
this.delegate=delegate;
}
#Override
public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
Collection<Cache> result = delegate.resolveCaches(context);
return result.stream().map(EncodingCache::new).collect(Collectors.toLlist());
}
}
The cache implementation
public class EncodingCache implements Cache {
private final Cache delegate;
public EncodingCache(Cache delegate) {
this.delegate=delegate;
}
public String getName() {
return delegate.getName();
}
public Object getNativeCache() {
return delegate.getNativeCache();
}
public void evict(Object key) {
delegate.evict(key)
}
public void put(Object key, Object value) {
Object encodedValue = encode(value);
this.delegate.put(key, encodedValue);
}
public <T> T get(Object key, Class<T> type) {
Object encodedValue = delegate.get(key, type);
return decode(encodedValue);
}
// Other Cache methods omitted but the pattern is the same
private Object encode(Object value) {
// encoding logic here
}
private Object decode(Object value) {
// decoding logic here
}
}
Then some configuration
#Configuration
#EnableCache
public void CacheConfiguration {
#Bean
public CacheResolver cacheResolver(CacheManager cacheManager) {
return new EncodingCacheResolver(SimpleCache.of(cacheManager));
}
}
Haven't tested the implementation, typed it from the top of my head. But this should more or less be what you need. You don't really need AOP for this.

Resources