Autowiring a spring managed bean in the resteasy providers - spring

I have a developing a webservice where in i need to validate a particular httpheader sent in the request against the database.
I want to use RestEasy provider for doing the same as the same functionality needs to be applied to all the service.
#Provider
#ServerInterceptor
public class TestValidationInterceptor implements PreProcessInterceptor {
#Autowired
DetailsDelegate detailsDelegate;
#Override
public ServerResponse preProcess(HttpRequest request, ResourceMethod method) throws Failure, WebApplicationException {
//code to get data using detailsDelegate.
return null;
}
}
public interface DetailsDelegate {
String BEAN_ID = "DetailsDelegate";
/**
* #param appName
* #return
* #throws BaseException
*/
ServiceInfo getServiceDetails(String appName) throws BaseException;
}
#Service("detailsDelegate")
public class DetailsDelegateImpl implements DetailsDelegate {
#Override
public ServiceInfo getServiceDetails(String appName) throws BaseException {
return null;
}
}
The detailsDelegate instance is not getting autowired and is null.
Can someone explain why I am facing this issue.

It's best to let spring chose it's bean names so change
#Service("detailsDelegate")
to
#Service
The autowire the interface :
#Auowired
DetailsDelegate
Finally make sure the package in which DetailsDelegate is defined is scanned in your config:
#ComponentScan("com.mypackage")
See http://docs.spring.io/spring-javaconfig/docs/1.0.0.M4/reference/html/ch06s02.html for examples

Related

Problem with GCP Secret Manager and Spring Boot app

Spring Boot (2.5.9) has problem to access password from the GCP Secret Manager using spring-cloud-gcp-starter-secretmanager ver 2.0.8 throwing error
AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'defaultFeignClientConfiguration': Injection of autowired dependencies failed; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [com.google.protobuf.ByteString$LiteralByteString] to type [java.lang.String]
for a passsword declared in the application.properties as
webservices.security.basic.user.password=${sm://my-password}
when I will replace it with regular string or even env variable it will work fine.
Failing part of the code looks like:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import feign.Retryer;
import feign.auth.BasicAuthRequestInterceptor;
import feign.codec.ErrorDecoder;
/**
* Default feign client configuration. Includes retry policies, basic auth user name and password, and HTTP status decoder.
* #author Greg Meyer
* #since 6.0
*/
public class DefaultFeignClientConfiguration
{
#Value("${webservices.retry.backoff.multiplier:3}")
protected double backoffMultiplier;
#Value("${webservices.retry.backoff.initialBackoffInterval:100}")
protected long initialBackoffInterval;
#Value("${webservices.retry.backoff.maxInterval:20000}")
protected long maxInterval;
#Value("${webservices.security.basic.user.name:}")
protected String user;
#Value("${webservices.security.basic.user.password:}")
protected String pass;
/**
* Creates an instance of the a the default HTTP status translator.
* #return An instance of the a the default HTTP status translator
*/
#Bean
public ErrorDecoder feignClientErrorDecoder()
{
return new DefaultErrorDecoder();
}
/**
* Creates an instance of BasicAuth interceptor configured with a username and password. This bean is only created if the
* "webservices.security.basic.user.name" property is set.
* #return An instance of BasicAuth interceptor configured with a username and password
*/
#Bean
#ConditionalOnProperty(name="webservices.security.basic.user.name", matchIfMissing=false)
public BasicAuthRequestInterceptor basicAuthRequestInterceptor()
{
return new BasicAuthRequestInterceptor(user, pass);
}
/**
* Creates an instance of a back off policy used in conjuntion with the retry policy.
* #return An instance of a back off policy
*/
#Bean
public LoadBalancedRetryFactory backOffPolciyFactory()
{
return new LoadBalancedRetryFactory()
{
#Override
public BackOffPolicy createBackOffPolicy(String service)
{
final ExponentialBackOffPolicy backoffPolicy = new ExponentialBackOffPolicy();
backoffPolicy.setMultiplier(backoffMultiplier);
backoffPolicy.setInitialInterval(initialBackoffInterval);
backoffPolicy.setMaxInterval(maxInterval);
return backoffPolicy;
}
};
}
/**
* Creates a default http retry policy.
* #return A default http retry policy.
*/
#Bean
public Retryer retryer()
{
/*
* Default retryer config
*/
return new Retryer.Default(200, 1000, 5);
}
}
Any thoughts?
The problem is that most likely, Feign autoconfiguration happens early on, before GcpSecretManagerEnvironmentPostProcessor had a chance to run and introduce ByteString converters.
Basically, the solution that works is to implement a Custom Converter which implements the Generic Conversion Service and register the Converter in the main method before calling SpringApplication.run.
public static void main(String[] args)
{
((DefaultConversionService)DefaultConversionService.getSharedInstance()).addConverter(new CustomConverter());
SpringApplication.run(STAApplication.class, args);
}
and custom converter:
#Component
public class CustomConverter implements GenericConverter {
#Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(ByteString.class, String.class));
}
#Override
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
if (sourceType.getType() == String.class) {
return source;
}
try {
source = ((ByteString) source).toString("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return source;
}
}

Spring Data Rest: #Autowire in Custom JsonDeserializer

I am trying to autowire a component into a custom JsonDeserializer but cannot get it right even with the following suggestions I found:
Autowiring in JsonDeserializer: SpringBeanAutowiringSupport vs HandlerInstantiator
Right way to write JSON deserializer in Spring or extend it
How to customise the Jackson JSON mapper implicitly used by Spring Boot?
Spring Boot Autowiring of JsonDeserializer in Integration test
My final goal is to accept URLs to resources in different microservices and store only the ID of the resource locally. But I don't want to just extract the ID from the URL but also verify that the rest of the URL is correct.
I have tried many things and lost track a bit of what I tried but I believe I tried everything mentioned in the links above. I created tons of beans for SpringHandlerInstantiator, Jackson2ObjectMapperBuilder, MappingJackson2HttpMessageConverter, RestTemplate and others and also tried with setting the SpringHandlerInstantiator in RepositoryRestConfigurer#configureJacksonObjectMapper.
I am using Spring Boot 2.1.6.RELEASE which makes me think something might have changed since some of the linked threads are quite old.
Here's my last attempt:
#Configuration
public class JacksonConfig {
#Bean
public HandlerInstantiator handlerInstantiator(ApplicationContext applicationContext) {
return new SpringHandlerInstantiator(applicationContext.getAutowireCapableBeanFactory());
}
}
#Configuration
public class RestConfiguration implements RepositoryRestConfigurer {
#Autowired
private Validator validator;
#Autowired
private HandlerInstantiator handlerInstantiator;
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
validatingListener.addValidator("beforeCreate", validator);
validatingListener.addValidator("beforeSave", validator);
}
#Override
public void configureJacksonObjectMapper(ObjectMapper objectMapper) {
objectMapper.setHandlerInstantiator(handlerInstantiator);
}
}
#Component
public class RestResourceURLSerializer extends JsonDeserializer<Long> {
#Autowired
private MyConfig config;
#Override
public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ServiceConfig serviceConfig = config.getServices().get("identity");
URI serviceUri = serviceConfig.getExternalUrl();
String servicePath = serviceUri.getPath();
URL givenUrl = p.readValueAs(URL.class);
String givenPath = givenUrl.getPath();
if (servicePath.equals(givenPath)) {
return Long.parseLong(givenPath.substring(givenPath.lastIndexOf('/') + 1));
}
return null;
}
}
I keep getting a NullPointerException POSTing something to the API endpoint that is deserialized with the JsonDeserializer above.
I was able to solve a similar problem by marking my deserializer constructor accept a parameter (and therefore removing the empty constructor) and marking constructor as #Autowired.
public class MyDeserializer extends JsonDeserializer<MyEntity> {
private final MyBean bean;
// no default constructor
#Autowired
public MyDeserializer(MyBean bean){
this.bean = bean
}
...
}
#JsonDeserialize(using = MyDeserializer.class)
public class MyEntity{...}
My entity is marked with annotation #JsonDeserialize so I don't have to explicitly register it with ObjectMapper.

Inject Spring Bean in Jackson VirtualBeanPropertyWriter

tldr; I want to add virtual fields while serializing the JPA entity into JSON using Jackson #JsonAppend. The value of the virtual fields must be determined via service managed by Spring. How do I inject my spring-managed service inside a Jackson class?
Technologies: Spring Boot 1.5.10, Spring Data Rest, JPA 2.1, Jackson 2.8.10
Details:
I have a Spring Data managed JPA entity:
#Entity
public class Stream {
...
}
I created a Custom Jackson module with a Mixin to add #JsonAppend virtual field as below:
#Bean
public Module customModule() {
return new CustomModule();
}
#Component
class CustomModule extends SimpleModule {
CustomModule() {
setMixInAnnotation(Stream.class, StreamMixin.class);
}
#JsonAppend(
props = {
#JsonAppend.Prop(name = "canEdit", value = ABACInspector.class)
}
)
abstract class StreamMixin {}
}
The ABACInspector class extends Jackson's VirtualBeanPropertyWriter to determine the value of the virtual field canEdit. If this class does not use a Spring service (sets hard-coded value for example), it works fine and the field shows up in REST API JSON response. But autowiring a Spring bean doesn't work and the object remains null.
#Component
class ABACInspector extends VirtualBeanPropertyWriter {
#Autowired
private PermissionEvaluator permissionEvaluator;
public ABACInspector() {
}
public ABACInspector(BeanPropertyDefinition propDef, Annotations contextAnnotations, JavaType declaredType) {
super(propDef, contextAnnotations, declaredType);
}
#Override
protected Object value(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
boolean permission = permissionEvaluator.hasPermission(authentication, bean, Action.STREAM_VIEW);
System.out.println("evaluated permission is " + permission);
return permission;
}
#Override
public VirtualBeanPropertyWriter withConfig(MapperConfig<?> config, AnnotatedClass declaringClass, BeanPropertyDefinition propDef, JavaType type) {
return new ABACInspector(propDef, null, type);
}
}
Below is the NPE error (because permissionEvaluator is never injected):
{"status":"INTERNAL_SERVER_ERROR","message":"Could not write JSON:
(was java.lang.NullPointerException); nested exception is com.fasterxml.jackson.databind.JsonMappingException:
(was java.lang.NullPointerException) (through reference chain: org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer$1[\"content\"]->com.example.streammanagement.Stream[\"canView\"])"
I am aware of Spring Data Rest's HalHandlerInstantiator that contains the AutowireCapableBeanFactory but I am not sure how/if that can help here. Refer DATAREST-840
Jackson internally calls withConfig function of your component to build VirtualBeanPropertyWriter.
So if you use breakpoints, you can see that first a component with injected bean is created, then withConfig function is called and new VirtualBeanPropertyWriter object is created which is used by jackson and of course does not have the injected bin (since you called the constructor manually).
So you can change it by this way:
#Component
class ABACInspector extends VirtualBeanPropertyWriter {
private PermissionEvaluator permissionEvaluator;
#Autowired
public ABACInspector(PermissionEvaluator permissionEvaluator) {
this.permissionEvaluator = permissionEvaluator;
}
public ABACInspector(BeanPropertyDefinition propDef, Annotations contextAnnotations, JavaType declaredType, PermissionEvaluator permissionEvaluator) {
super(propDef, contextAnnotations, declaredType);
this.permissionEvaluator = permissionEvaluator;
}
#Override
protected Object value(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
boolean permission = permissionEvaluator.hasPermission(authentication, bean, Action.STREAM_VIEW);
System.out.println("evaluated permission is " + permission);
return permission;
}
#Override
public VirtualBeanPropertyWriter withConfig(MapperConfig<?> config, AnnotatedClass declaringClass, BeanPropertyDefinition propDef, JavaType type) {
return new ABACInspector(propDef, null, type, permissionEvaluator);
}
}

Spring Zuul: Dynamically disable a route to a service

I'm trying to disable a Zuul route to a microservice registered with Eureka at runtime (I'm using spring boot).
This is an example:
localhost/hello
localhost/world
Those two are the registered microservices. I would like to disable the route to one of them at runtime without shutting it down.
Is there a way to do this?
Thank you,
Nano
Alternatively to using Cloud Config, custom ZuulFilter can be used. Something like (partial implementation to show the concept):
public class BlackListFilter extends ZuulFilter {
#Override
public String filterType() {
return "pre";
}
...
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
String uri = ctx.getRequest().getRequestURI();
String appId = uri.split("/")[1];
if (blackList.contains(appId)) {
ctx.setSendZuulResponse(false);
LOG.info("Request '{}' from {}:{} is blocked",
uri, ctx.getRequest().getRemoteHost(), ctx.getRequest().getRemotePort());
}
return null;
}
}
where blackList contains list of application IDs (Spring Boot application name) managed for example via some RESTful API.
After a lot of efforts I came up with this solution. First, I used Netflix Archaius to watch a property file. Then I proceeded as follows:
public class ApplicationRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
public ApplicationRouteLocator(String servletPath, ZuulProperties properties) {
super(servletPath, properties );
}
#Override
public void refresh() {
doRefresh();
}
}
Made the doRefresh() method public by extending SimpleRouteLocator and calling its method in the overridden one of the interface RefreshableRouteLocator.
Then I redefined the bean RouteLocator with my custom implementation:
#Configuration
#EnableConfigurationProperties( { ZuulProperties.class } )
public class ZuulConfig {
public static ApplicationRouteLocator simpleRouteLocator;
#Autowired
private ZuulProperties zuulProperties;
#Autowired
private ServerProperties server;
#Bean
#Primary
public RouteLocator routeLocator() {
logger.info( "zuulProperties are: {}", zuulProperties );
simpleRouteLocator = new ApplicationRouteLocator( this.server.getServletPrefix(),
this.zuulProperties );
ConfigurationManager.getConfigInstance().addConfigurationListener( configurationListener );
return simpleRouteLocator;
}
private ConfigurationListener configurationListener =
new ConfigurationListener() {
#Override
public void configurationChanged( ConfigurationEvent ce ) {
// zuulProperties.getRoutes() do something
// zuulProperties.getIgnoredPatterns() do something
simpleRouteLocator.refresh();
}
}
}
Every time a property in the file was modified an event was triggered and the ConfigurationEvent was able to deal with it (getPropertyName() and getPropertyValue() to extract data from the event). Since I also Autowired the ZuulProperties I was able to get access to it. With the right rule I could find whether the property of Zuul
zuul.ignoredPatterns
was modified changing its value in the ZuulProperties accordingly.
Here refresh context should work (as long as you are not adding a new routing rule or removing a currently existing one), if you are adding or removing routing rules, you have to add a new bean for ZuulProperties and mark it with #RefreshScope, #Primary.
You can autowire refreshEndpoint bean for example and apply refreshEndpoint.refresh() on the listener.
Marking a custom RouteLocator as primary will cause problems as zuul already has bean of same type marked as primary.

register multiple resource instances of same type

I have a resource endpoint that injects a #PathParam into constructor, i.e., different instance per #PathParam value. It all works fine in Jetty. But now I'm trying to write unit tests using Jersey Test Framework, and it seems that the test framework only supports one registered endpoint per type.
So if I do something like this:
#Path("/users")
public class MyResource {
public MyResource(#PathParam("userId") int userId) {
}
#Path("{userId}")
public String get() {
}
}
public class MyTest extends JerseyTestNg.ContainerPerClassTest {
#Override
protected Application configure() {
return new ResourceConfig()
.register(new MyResource(1))
.register(new MyResource(2));
}
#Test
public void test2() {
target("/users/1").request().get();
}
#Test
public void test2() {
target("/users/2").request().get();
}
}
I see that both test1 and test2 are invoking the instance of MyResource(1). Is this expected? Is there a solution to invoke the correct instance?
You should register the resource as a class. Jersey will create it for you. And handle all the injections.
"The example I posted is dumbed down. In reality, my resource constructor has another injected object that I need to mock. So how would I specify a mocked object parameter for the constructor?"
You can do something like
#Mock
private Service service;
#Override
public ResourceConfig configure() {
MockitoAnnotations.initMocks(this);
return new ResourceConfig()
.register(MyResource.class)
.register(new AbstractBinder() {
#Override
protected configure() {
bind(service).to(Service.class);
}
});
}
#Test
public void test() {
when(service.getSomething()).thenReturn("Something");
// test
}
Assuming you are already using the built in HK2 DI, and have an #Inject annotation on the constructor of your resource class, this should work. In the AbstractBinder we are making the mock object injectable. So now Jersey can inject it into your resource.
See Also:
Jersey - How to mock service

Resources