swagger-ui not working with custom XML ObjectMapper - spring-boot

I'm working on a spring boot app that should have swagger-ui enabled.
When accessing http://localhost:8080/swagger-ui.html there is an error popup:
"Unable to infer base url ..."
Additionaly, http://localhost:8080/v2/api-docs shows:
error on line 1 at column 1: Document is empty
The source-code of this page is a json, but it's requested as Content-Type application/xhtml+xml;charset=UTF-8
The cause of this seems to be my custom Jackson configuration:
#Configuration
public class JacksonConfig {
#Bean
public MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter() {
return new MappingJackson2XmlHttpMessageConverter(objectMapper());
}
#Bean
public ObjectMapper objectMapper() {
JacksonXmlModule xmlModule = new JacksonXmlModule();
xmlModule.setDefaultUseWrapper(false);
XmlMapper objectMapper = new XmlMapper(xmlModule);
objectMapper
.registerModule(new ParameterNamesModule())
.registerModule(new Jdk8Module())
.registerModule(new JavaTimeModule());
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.UPPER_CAMEL_CASE);
objectMapper
.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false)
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
return objectMapper;
}
}
With the following dependency:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
The problem is also described here: https://github.com/springfox/springfox/issues/1835
So my question is: How do I specify the priority of the jackson message converter to get swagger-ui working?

I just stumbled upon the solution while re-reading my own question.
Just add this to the above JacksonConfig class (don't know if ordering is important, but it works).
#Bean
public MappingJackson2HttpMessageConverter jsonConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
jsonConverter.setObjectMapper(objectMapper);
return jsonConverter;
}

Inside the swagger code, it checks to see if an ObjectMapper exists, and if not, it creates one to use. If a bean has been created that uses either an ObjectMapper or XMLMapper, then Swagger will use this instance, and get corrupted. The way round this is to create a bean for the ObjectMapper and use the #Primary annotation, then create the XMLMapper bean you want to use.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
#Configuration
public class MessageResponseXMLMapper {
#Bean
#Primary
public ObjectMapper customObjectMapper() {
return new ObjectMapper();
}
#Bean(name="customXmlMapper")
public XmlMapper customXmlMapper() {
return new Jackson2ObjectMapperBuilder()
.indentOutput(true)
.createXmlMapper(true)
.propertyNamingStrategy(PropertyNamingStrategy.UPPER_CAMEL_CASE)
.build(); }
}
Hope this helps

Related

Customise JSON date formatting of JSON for spring-mvc (non-boot)?

I am converting my app to get rid of spring-boot, it now uses only Spring (5.3).
I've added the #EnableWebMvc configuration and I have my endpoints working properly for the most part - they return the data I want as JSON.
Previously, I customised the date format with the spring-boot property: spring.jackson.date-format=yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
In the new pure-spring app though, it's regressed back serializing to a long value.
I've tried the following, but it doesn't seem to even use these beans at all:
#Bean
public ObjectMapper objectMapper() {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
ObjectMapper dateFormatMapper = new ObjectMapper();
dateFormatMapper.setDateFormat(dateFormat);
return dateFormatMapper;
}
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2JsonView(){
var converter = new MappingJackson2HttpMessageConverter();
converter.getObjectMapper().setDateFormat(
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") );
return converter;
}
I'm looking to customise the format globally, not on a per-field basis.
What would be the equivalent of spring.jackson.date-format for pure Spring #EnableWebMvc setup?
You can customize MappingJackson2HttpMessageConverter by using WebMvcConfigurer with #EnableWebMvc.
For example:
#Configuration
#EnableWebMvc
public class YourConfiguration implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
.indentOutput(true)
.dateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"));
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
}
For more information, please see 1.11.7. Message Converters - Web on Servlet Stack - docs.spring.io.

Spring Jackson custom Deserializer does not called

I have a Spring project, and I try to add a custom deserializer to deserialize Date properties depend on their format.
If I use it as annotation on Date property, it works fine.
But if I add the deserializer to my object mapper, it does not called when Jackson deserialize a date.
I try to apply my custom deserializer like this:
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Date.class, new DateDeserializer());
mapper.registerModule(module);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
mapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
return mapper;
}
I don't want to apply an annotation on Date properties every time, I want to use this deserializer by default.
What I do wrong?
Thanks the help for everyone.
Finally I found the answer at spring.io.
#Configuration
#EnableWebMvc
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.deserializerByType(Date.class, new DateDeserializer());
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
}
Given that if you use the deserializer in an annotation on Date property then I would say that this ObjectMapper is not being used for deserialization. Try the following:
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
jsonConverter.setObjectMapper(objectMapper);
return jsonConverter;
}
I have doubt how you try to use this ObjectMapper bean inside your application.
I trust you already know that this bean is need to created inside a Configuration class. If not your bean will not register in the context. Like this for example,
#Configuration
public class MapperConfig {
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Date.class, new DateDeserializer());
mapper.registerModule(module);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
mapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
return mapper;
}
}
The second problem might be how you try use this ObjectMapper bean. If your create new instance of ObjectMapper like this ObjectMapper objectMapper = new ObjectMapper();, that instance will not have your custom deserializer and stuff. What you can do is #Autowire the ObjectMapper instance that you have already created.

How can I disable Spring RestTemplate following a HTTP redirect response?

I'm integrating with an ancient service that is adds jsessionid to the URL and redirects to it. I'm using RestTemplate to talk to the service.
The problem is that it follows the redirect forever, since I'm not setting the jsession cookie.
How do I turn off following the redirects in Spring RestTemplate?
I figured out one way to do it, don't know if this is the preferred way to do it.
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
final HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
CloseableHttpClient build =
HttpClientBuilder.create().disableRedirectHandling().build();
factory.setHttpClient(build);
restTemplate.setRequestFactory(factory);
return restTemplate;
}
You can also write your own HttpRequestFactory class and override the default behaviour:
class CustomClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
#Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
super.prepareConnection(connection, httpMethod);
connection.setInstanceFollowRedirects(false);
}
}
#Configuration
public class AppConfig {
#Bean
public RestTemplate httpClient(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofSeconds(10))
.setReadTimeout(Duration.ofSeconds(10))
.requestFactory(CustomClientHttpRequestFactory.class)
.build();
}
}
Adding my solution as I found the others above not fitting the current Spring 5.x and Spring Boot 2.7.
The important aspect is that Spring can be configured with around 6 different HTTP client libraries. The way to configure them differs, and is done by using the client lib's API directly.
Below is an example for Apache HttpClient (historically called HttpCompoments) used to create a RestTemplate for a test. The syntax is Kotlin.
import org.apache.http.client.HttpClient
import org.apache.http.impl.client.HttpClients
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory
import org.springframework.web.client.RestTemplate
#Bean #Qualifier("forTests")
fun restTemplateHttpsWithTestTrustStore(builder: RestTemplateBuilder): RestTemplate {
val httpClient: HttpClient = HttpClients.custom()
.disableRedirectHandling()
.build()
return builder.requestFactory { HttpComponentsClientHttpRequestFactory(httpClient) }.build()
}
And then the test:
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
)
class HttpsWorksTest {
#LocalServerPort private var port = 0
#Autowired #Qualifier("forTests") private lateinit var restTemplateTrustStore: RestTemplate
#Test
fun httpsSelfRequestTest() {
var url = "$proto://localhost:$port/someRediirectingEndpoint"
log.info("Requesting: GET $url")
var response = restTemplateTrustStore.getForEntity(url, String::class.java)
Assertions.assertThat(response.statusCode).isEqualTo(HttpStatus.SEE_OTHER)
}
}

Injecting configuration dependency

I am creating a cache client wrapper using spring framework. This is to provide cache layer to our application. Right now, we are using redis. I have found out that spring-data-redis library is very good for creating my wrapper.
My application will pass a configuration POJO to my wrapper and will then use the interface that I will provide.
spring-data-redis provides an easy way to access redis using two variables.
RedisConnectionFactory
RedisTemplate<String, Object>
Although, I will be providing a better interface to my application with my interface functions like:
public Object getValue( final String key ) throws ConfigInvalidException;
public void setValue( final String key, final Object value ) throws ConfigInvalidException;
public void setValueWithExpiry(final String key, final Object value, final int seconds, final TimeUnit timeUnit) throws ConfigInvalidException;
I still want to provide RedisConnectionFactory and RedisTemplate beans.
My question is how to initialize my wrapper application with this configuration POJO?
Currently my configuration looks like this:
import java.util.List;
public class ClusterConfigurationProperties {
List<String> nodes;
public List<String> getNodes() {
return nodes;
}
public void setNodes(List<String> nodes) {
this.nodes = nodes;
}
}
And my AppConfig.java looks like this:
import com.ajio.Exception.ConfigInvalidException;
import com.ajio.configuration.ClusterConfigurationProperties;
import com.ajio.validator.Validator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
#Configuration
public class AppConfig {
#Autowired
private ClusterConfigurationProperties clusterConfigurationProperties;
#Autowired
private Validator validator;
#Bean
ClusterConfigurationProperties clusterConfigurationProperties() {
return null;
}
#Bean
Validator validator() {
return new Validator();
}
#Bean
RedisConnectionFactory connectionFactory() throws ConfigInvalidException {
if (clusterConfigurationProperties == null)
throw new ConfigInvalidException("Please provide a cluster configuration POJO in context");
validator.validate(clusterConfigurationProperties);
return new JedisConnectionFactory(new RedisClusterConfiguration(clusterConfigurationProperties.getNodes()));
}
#Bean
RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) throws ConfigInvalidException {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory());
redisTemplate.setKeySerializer( new StringRedisSerializer() );
redisTemplate.setHashValueSerializer( new GenericToStringSerializer<>( Object.class ) );
redisTemplate.setValueSerializer( new GenericToStringSerializer<>( Object.class ) );
return redisTemplate;
}
}
Here I am expecting a ClusterConfigurationProperties POJO as a bean in application which will be using the interface of wrapper.
But to compile my wrapper, I have created a null bean itself. Then when application uses it, there will be two beans, one of application and one of wrapper.
How should I resolve this problem?
Actually what i wanted was to have cluster config as a bean in my client application. For that i dont need to declare #autowire clusterconfig in my wrapper application. Instead should take cluster config as a parameter in the method, so that the client will pass cluster config object when creating bean. And the bean which is created in client code should have code for creating redis connection factory.
But all this i was writing was to make my client unknown of redis. So, better solution is to have wrapper class which takes cluster config pojo and create redis connection factory etc. And client should create this wrapper as a bean.
Very poor concept of spring and design patterns lead me to this mistake.

Corda RPC JacksonSupport.createDefaultMapper to use ObjectMapper in Spring client

How to register newly introduced Corda RPC ObjectMapper in Spring Boot?
Even after having below code in #Configuration class Jackson failing to serialize Party object to JSON string.
#Bean
public JsonComponentModule jsonComponentModule() {
return new JsonComponentModule();
}
#Bean
#Primary
public ObjectMapper cordaRpcObjectMapper(NodeRPCConnection rpc) {
ObjectMapper objectMapper = JacksonSupport.createDefaultMapper(rpc.getProxy(), new JsonFactory(), true);
objectMapper.registerModule(jsonComponentModule());
return objectMapper;
}
After some tweaks I'm successfully able to register Corda RPC ObjectMapper with Jackson with below code.
//Register any other custom (de)Serializer classes.
#Bean
public Module jsonComponentModule() {
return new JsonComponentModule();
}
//Force Spring/Jackson to use only provided Corda ObjectMapper for serialization.
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(#Autowired NodeRPCConnection rpcConnection) {
ObjectMapper mapper = JacksonSupport.createDefaultMapper(rpcConnection.getProxy()/*, new JsonFactory(), true*/);
mapper.registerModule(jsonComponentModule());
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(mapper);
return converter;
}

Resources