Spring type conversion not taking effect - spring-mvc

I have a lot of Enums implementing an Interface called Codeable. I want to do a reverse look up when deserializing from json and trying to use a ConverterFactory
public class StringToCodeableConverterFactory implements ConverterFactory<String, Codeable> {
#Override
public <T extends Codeable> Converter<String, T> getConverter(
Class<T> targetType) {
return new StringToCodeableConverter<T>(targetType);
}
private final class StringToCodeableConverter<T extends Codeable> implements Converter<String, T> {
private Class<T> enumType;
public StringToCodeableConverter(Class<T> enumType) {
this.enumType = enumType;
}
#Override
public T convert(String source) {
return CodeableUtil.get(this.enumType, source);
}
}
}
Here's the spring config
<!-- Custom Converters from String to Java Type-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.duetto.model.StringToCodeableConverterFactory" />
</list>
</property>
</bean>
<mvc:annotation-driven conversion-service="conversionService">
After some digging, I figured out Spring is taking the default StringToEnumConverterFactory instead of my StringToCodeableConverterFactory, why is it this way? How can I make my take precedence over the other one?

What I noticed happening was that the DefaultConversionService's defaults included a StringToEnumConversion which appears first in the service's List of possible converters. As such, mine was never being hit, and the standard Enum conversion was being attempted every time.
My workaround was to:
Unregister the default string-to-enum converter - registry.removeConvertible(String.class, Enum.class) where registry is an instance of FormatterRegistry
Write a string-to-mycustomenum converter
Write an all-encompassing string-to-enum converter that did a type check (MyEnumType.class.isAssignableFrom(targetType)) and delegated to either my custom converter or the default string-to-enum converter depending on the result
Note this approach has several problems, among them: StringToEnumConverter is a package-private class so I had to copy-paste it into my own code base. Additionally, this can't be the desired approach to solving this problem; it isn't very "springy".
Would love to hear alternative answers for this.
Worth noting, I'm using Spring 3.2.6
Major Update
I found a much cleaner way, note that I'm using annotation config rather than xml but the principals should be the same.
In Spring's documentation, I found:
GenericConversionService is a generic implementation designed to be
explicitly configured, either programatically or declaratively as a
Spring bean. DefaultConversionService is a subclass that pre-registers
the common Converters in the core.converter package as a convenience.
So, I now have overrides configured as follows:
#Override
public FormattingConversionService mvcConversionService() {
// use FormattingConversionService here rather than GenericFormattingConversionService (the default)
// because it does not automatically register defaults
FormattingConversionService conversionService = new FormattingConversionService();
addFormatters(conversionService);
return conversionService;
}
#Override
protected void addFormatters(FormatterRegistry registry) {
// register custom enum handler first
registry.addConverterFactory(new MyCustomEnumConverterFactory());
// now add in spring's defaults
DefaultConversionService.addDefaultConverters(registry);
DefaultFormattingConversionService.addDefaultFormatters(registry);
}
Now everything's working and it feels significantly less hacky.

Related

Kafka Streams Serdes having nested generic doesn't work

I have following code which uses functional style to define two functions for kafka topics
#Bean
public Function<KStream<String, CloudEvent<ClassA>>, KStream<String, CloudEvent<ClassB>>> method1() {
....... //lambda
}
#Bean
public Function<KStream<String, CloudEvent<ClassB>>, KStream<String, CloudEvent<ClassC>>> method2() {
...... //lambda
}
For these two functions I define serdes so
#Bean
public Serde<CloudEventMessage<ClassA>> classASerde(ObjectMapper mapper, Validator validator) {
return StreamsSerdes.classASerde(mapper,validator);
}
#Bean
public Serde<CloudEventMessage<ClassB>> classBSerde(ObjectMapper mapper, Validator validator) {
return StreamsSerdes.classBSerde(mapper,validator);
}
This construction doesn't work as at runtime spring tries to deserialize CloudEvent<ClassB> with Serde of CloutEvent<ClassA>. Is there someway to give hint to use the correct serde for method1 and method2 ?
Secondly I could bypass the above issues by mentioning Serdes in application.properties
spring.application.cloud.stream.kafka.streams.bindings.method1-in-0.consumer.valueSerde=package.serde.StreamsSerdes$ClassASerde
spring.application.cloud.stream.kafka.streams.bindings.method2-in-0.consumer.valueSerde=package.serde.StreamsSerdes$ClassBSerde
However now I get other issues as these Serde classes don't have default constructor. I do need ObjectMapper, Validator from Spring to inject beans (#Service) to perfrom converstions/validations during deserialization.
Has anyone come across similar issues or perhaps have ideas how to resolve them ?
Thanks
I think it is a gap that the nested generics are not working right now in the binder. Do you mind creating an issue in the repository and linking this thread?
As to the second issue that you are running into when providing properties in application.properties, you can try using a workaround. The Serde interface has a configure method that takes a map.
default void configure(Map<String, ?> configs, boolean isKey) {
// intentionally left blank
}
Override this method in your Serde implementation and set those bean objects under some keys.
ObjectMapper mapper;
Validator validator;
#Override
public void configure(Map<String, ?> configs, boolean isKey) {
this.mapper = (ObjectMapper) configs.get("mapper.key");
this.validator = (Validator) configs.get("validator.key");
}
You need to remove accessing them from the constructor and use those fields directly for deserialization and serialization.
Then you provide this bean in your application to populate the map:
#Bean
public StreamsBuilderFactoryBeanCustomizer streamsBuilderFactoryBeanCustomizer(ObjectMapper mapper, Validator validator) {
return factoryBean -> {
factoryBean.getStreamsConfiguration().put("mappeer.key", mapper);
factoryBean.getStreamsConfiguration().put("validator.key", validator);
};
}
I haven't tried this code in an application, but it is something that you can try and see if it works with your code.

aop aspects as mock in spring test

I came across an interesting article: AOP Aspects as mocks in JUnit
Since I have requirement to mock multiple final and private static variables, I am planning to use AOP in place of reflection or PowerMockito as they are causing issues with SpringJUnit4ClassRunner.
Is there any way I can use #Aspect for test classes without using the annotation #EnableAspectJAutoProxy? (I want to use an aspect targeting class X only in one test case.)
This is a sample of what I want to do.
The question is answered(adding for discussion on what could be done)
//External class
public final class ABC(){
public void method1() throws Exception {}
}
#Service
public void DestClass() {
private static final ABC abc = new ABC();
public Object m() {
// code (...)
try {
abc.method1();
}
catch(Exception e) {
// do something (...)
return null;
}
// more code (...)
}
}
Spring framework allows to programmatically create proxies that advise target objects , without configuring through #EnableAspectJAutoProxy or <aop:aspectj-autoproxy>
Details can be found in the documentation section : Programmatic Creation of #AspectJ Proxies and the implementation is pretty simple.
Example code from the documentation.
// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
// add an aspect, the class must be an #AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);
// you can also add existing aspect instances, the type of the object supplied must be an #AspectJ aspect
factory.addAspect(usageTracker);
// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();
Please note that with Spring AOP , only method executions can be adviced. Excerpt from the documentation
Spring AOP currently supports only method execution join points
(advising the execution of methods on Spring beans). Field
interception is not implemented, although support for field
interception could be added without breaking the core Spring AOP APIs.
If you need to advise field access and update join points, consider a
language such as AspectJ.
The document shared with the question is about aspectj and without providing the sample code to be adviced it is hard to conclude if the requriement can acheived through Spring AOP. The document mentions this as well.
One example of the integration of AspectJ is the Spring framework,
which now can use the AspectJ pointcut language in its own AOP
implementation. Spring’s implementation is not specifically targeted
as a test solution.
Hope this helps.
--- Update : A test case without using AOP ---
Consider the external Class
public class ABCImpl implements ABC{
#Override
public void method1(String example) {
System.out.println("ABC method 1 called :"+example);
}
}
And the DestClass
#Service
public class DestClass {
private static final ABC service = new ABCImpl();
protected ABC abc() throws Exception{
System.out.println("DestClass.abc() called");
return service;
}
public Object m() {
Object obj = new Object();
try {
abc().method1("test");
} catch (Exception e) {
System.out.println("Exception : "+ e.getMessage());
return null;
}
return obj;
}
}
Following test class autowires the DestClass bean with overridden logic to throw exception . This code can be modified to adapt to your requirement.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { DestClassSpringTest.TestConfiguration.class })
public class DestClassSpringTest {
#Configuration
static class TestConfiguration {
#Bean
public DestClass destClass() {
return new DestClass() {
protected ABC abc() throws Exception {
// super.abc(); // not required . added to demo the parent method call
throw new Exception("Custom exception thrown");
}
};
}
}
#Autowired
DestClass cut;
#Test
public void test() {
Object obj = cut.m();
assertNull(obj);
}
}
Following will be the output log
DestClass.abc() called // this will not happen if the parent method call is commented in DestClassSpringTest.TestConfiguration
Exception : Custom exception thrown
The article you are referring to is using full AspectJ, not Spring AOP. Thus, you do not need any #EnableAspectJAutoProxy for that, just
either the AspectJ load-time weaver on the command line when running your test via -javaagent:/path/to/aspectjweaver.jar
or the AspectJ compiler activated when compiling your tests (easily done via AspectJ Maven plugin if you use Maven)
Both approaches are completely independent of Spring, will work in any project and even when using Spring also work when targeting execution of third party code because no dynamic proxies are needed unlike in Spring AOP. So there is no need to make the target code into a Spring bean or to create a wrapper method in your application class for it. When using compile-time weaving you can even avoid weaving into the third party library by using call() instead of execution() pointcut. Spring AOP only knows execution(), AspectJ is more powerful.
By the way: Unfortunately both your question and your comment about the solution you found are somewhat fuzzy and I do not fully understand your requirement. E.g. you talked about mocking final and private static variables, which would also be possible in other ways with AspectJ by using set() and/or get() pointcuts. But actually it seems you do not need to mock the field contents, just stub the results of method calls upon the objects assigned to those fields.

How to use ResourceProcessorHandlerMethodReturnValueHandler in a spring-hateos project

When using spring-data-rest there is a post processing of Resource classes returned from Controllers (e.g. RepositoryRestControllers). The proper ResourceProcessor is called in the post processing.
The class responsible for this is ResourceProcessorHandlerMethodReturnValueHandler which is part of spring-hateoas.
I now have a project that only uses spring-hateoas and I wonder how to configure ResourceProcessorHandlerMethodReturnValueHandler in such a scenario. It looks like the auto configuration part of it still resides in spring-data-rest.
Any hints on how to enable ResourceProcessorHandlerMethodReturnValueHandler in a spring-hateoas context?
I've been looking at this recently too, and documentation on how to achieve this is non-existent. If you create a bean of type ResourceProcessorInvokingHandlerAdapter, you seem to lose the the auto-configured RequestMappingHandlerAdapter and all its features. As such, I wanted to avoid using this bean or losing the WebMvcAutoConfiguration, since all I really wanted was the ResourceProcessorHandlerMethodReturnValueHandler.
You can't just add a ResourceProcessorHandlerMethodReturnValueHandler via WebMvcConfigurer.addReturnValueHandlers, because what we need to do is actually override the entire list, as is what happens in ResourceProcessorInvokingHandlerAdapter.afterPropertiesSet:
#Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
// Retrieve actual handlers to use as delegate
HandlerMethodReturnValueHandlerComposite oldHandlers = getReturnValueHandlersComposite();
// Set up ResourceProcessingHandlerMethodResolver to delegate to originally configured ones
List<HandlerMethodReturnValueHandler> newHandlers = new ArrayList<HandlerMethodReturnValueHandler>();
newHandlers.add(new ResourceProcessorHandlerMethodReturnValueHandler(oldHandlers, invoker));
// Configure the new handler to be used
this.setReturnValueHandlers(newHandlers);
}
So, without a better solution available, I added a BeanPostProcessor to handle setting the List of handlers on an existing RequestMappingHandlerAdapter:
#Component
#RequiredArgsConstructor
#ConditionalOnBean(ResourceProcessor.class)
public class ResourceProcessorHandlerMethodReturnValueHandlerConfigurer implements BeanPostProcessor {
private final Collection<ResourceProcessor<?>> resourceProcessors;
#Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof RequestMappingHandlerAdapter) {
RequestMappingHandlerAdapter requestMappingHandlerAdapter = (RequestMappingHandlerAdapter) bean;
List<HandlerMethodReturnValueHandler> handlers =
requestMappingHandlerAdapter.getReturnValueHandlers();
HandlerMethodReturnValueHandlerComposite delegate =
handlers instanceof HandlerMethodReturnValueHandlerComposite ?
(HandlerMethodReturnValueHandlerComposite) handlers :
new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
requestMappingHandlerAdapter.setReturnValueHandlers(Arrays.asList(
new ResourceProcessorHandlerMethodReturnValueHandler(delegate,
new ResourceProcessorInvoker(resourceProcessors))));
return requestMappingHandlerAdapter;
}
else return bean;
}
}
This has seemed to work so far...

set database pulled application constants on startup

I have a lot of dropdown menus in my application. The contents of these dropdown menus is read from the database.
Currently, to avoid pulling these values back every-time I want to use them, I pull them back once when I first need them and store them in the session after that.
This is not good. I do want to be storing them in the session as there are a number of them, and I think it's a bit, well, clunky. I would like to ideally (assuming you don't disagree), store these 'constants' (they aren't traditional constants, but they won't change for the duration of the deployment), in the application scope, so they need only be pulled back from the database once per deployment.
What I'm wondering is, what is the way to store these dropdown values in the application context at startup?
There is an interface by the name of ServletContextListener which provides a hook into the initialization of your servlet context. The ServletContext is your application's context, ie. its configuration.
One way to do what you are describing is to implement this interface and register the ServletContextListener either in web.xml or by annotating the class with #WebListener.
In the contextInitialized() method, you would pull the constants, set them up in any way you needed to and then put them in the ServletContext as attributes
#WebListener
public static class MyListener implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
int someConstant = 42;
context.setAttribute("myConstant", someConstant);
}
#Override
public void contextDestroyed(ServletContextEvent sce) {
...
}
}
You can then access these constants anywhere you have access to the ServletContext.
In a Servlet or Filter, you can get it from the ServletRequest
HttpServletRequest request = ....;
request.getServletContext();
You also have access to it from the init() methods of Servlet and Filter if you want to add more attributes there.

Controller Inheritance and Ambiguous Mappings with URL Versioning in Spring MVC

I am trying to setup versioned services with Spring MVC, using inheritance to extend older controllers to avoid rewriting unchanged controller methods. I've based my solution on a previous question about versioning services, however I've run into a problem with ambiguous mappings.
#Controller
#RequestMapping({"/rest/v1/bookmark"})
public class BookmarkJsonController {
#ResponseBody
#RequestMapping(value = "/write", produces = "application/json", method = RequestMethod.POST)
public Map<String, String> writeBookmark(#RequestParam String parameter) {
// Perform some operations and return String
}
}
#Controller
#RequestMapping({"/rest/v2/bookmark"})
public class BookmarkJsonControllerV2 extends BookmarkJsonController {
#ResponseBody
#RequestMapping(value = "/write", produces = "application/json", method = RequestMethod.POST)
public BookmarkJsonModel writeBookmark(#RequestBody #Valid BookmarkJsonModel bookmark) {
// Perform some operations and return BookmarkJsonModel
}
}
With this setup I get IllegalStateException: Ambiguous mapping found. My thought regarding this is that because I have two methods with different return/argument types I have two methods in BookmarkJsonControllerV2 with the same mapping. As a workaround I attempted to override writeBookmark in BookmarkJsonControllerV2 without any request mapping:
#Override
public Map<String, String> writeBookmark(#RequestParam String parameter) {
return null; // Shouldn't actually be used
}
However, when I compiled and ran this code I still got the exception for an ambiguous mapping. However, when I hit the URL /rest/v2/bookmark/write I got back an empty/null response. Upon changing return null to:
return new HashMap<String, String>() {{
put("This is called from /rest/v2/bookmark/write", "?!");
}};
I would receive JSON with that map, indicating that despite not having any request mapping annotation, it is apparently "inheriting" the annotation from the super class. At this point, my only "solution" to future-proofing the extension of the controllers is to make every controller return Object and only have the HttpServletRequest and HttpServletResponse objects as arguments. This seems like a total hack and I would rather never do this.
So is there a better approach to achieve URL versioning using Spring MVC that allows me to only override updated methods in subsequent versions or is my only real option to completely rewrite each controller?
For whatever reason, using the #RequestMapping annotation was causing the ambiguous mapping exceptions. As a workaround I decided to try using springmvc-router for my REST services which would allow me to leverage inheritance on my controller classes so I would not have to reimplement endpoints that did not change between versions as desired. My solution also allowed me to continue using annotation mappings for my non-REST controllers.
Note: I am using Spring 3.1, which has different classes for the handler mappings than previous versions.
The springmvc-router project brings the router system from the Play framework over to Spring MVC. Inside of my application-context.xml, the relevant setup looks like:
<mvc:annotation-driven/>
<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" />
<bean class="org.resthub.web.springmvc.router.RouterHandlerMapping">
<property name="routeFiles">
<list>
<value>routes/routes.conf</value>
</list>
</property>
<property name="order" value="0" />
</bean>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="order" value="1" />
</bean>
This allows me to continue using my annotated controllers alongside the router. Spring uses a chain-of-responsibility system, so we can assign multiple mapping handlers. From here, I have a router configuration like so:
# Original Services
POST /rest/bookmark/write bookmarkJsonController.write
POST /rest/bookmark/delete bookmarkJsonController.delete
# Version 2 Services
POST /rest/v2/bookmark/write bookmarkJsonControllerV2.write
POST /rest/v2/bookmark/delete bookmarkJsonControllerV2.delete
Alongside controllers looking like:
#Controller
public class BookmarkJsonController {
#ResponseBody
public Map<String, Boolean> write(#RequestParam String param) { /* Actions go here */ }
#ResponseBody
public Map<String, Boolean> delete(#RequestParam String param) { /* Actions go here */ }
}
#Controller
public class BookmarkJsonControllerV2 extends BoomarkJsonController {
#ResponseBody
public Model write(#RequestBody Model model) { /* Actions go here */ }
}
With a configuration like this, the URL /rest/v2/bookmark/write will hit the method BookmarkJsonControllerV2.write(Model model) and the URL /rest/v2/bookmark/delete will hit the inherited method BookmarkJsonController.delete(String param).
The only disadvantage from this comes from having to redefine entire routes for new versions, as opposed to changing the #RequestMapping(value = "/rest/bookmark") to #RequestMapping(value = "/rest/v2/bookmark") on the class.

Resources