Springfox-boot-starter swagger Instant handling - java

I have a problem with swagger documentation using SpringBoot with Springfox-boot-starter.
I use java.time.Instant wrapped in java.util.Optional in my REST API which works fine:
#GetMapping("/{subscriptionId}/{variableAlias}")
public PaginatedResultDTO<MonitoredVariableDTO> getReportedVariables(
#PathVariable String subscriptionId,
#PathVariable String variableAlias,
Optional<Instant> from,
Optional<Instant> to) { ... }
But for some reason, Swagger documentation cannot handle the Optional type correctly and seems to handle it through reflection as EpochSeconds and Nano attributes instead of one field:
I would like to make swagger expect from and to instants in ISO format, just like Spring does and how I use it in Insomnia:
When I tried to remove the Optional wrapper, it seems to work
Is there a way to make this work with the Optional? Thanks for any advice!
Spring boot version:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath />
</parent>
Springfox-boot-starter version
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>

We had exactly the same problem that you.
We solved it with this SpringFox configuration:
#Configuration
#EnableSwagger2
public class SpringfoxConfiguration {
#Value("${api-doc.version}")
private String apiInfoVersion;
#Autowired
private TypeResolver typeResolver;
#Bean
public Docket customDocket(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("xxx")
//Some other code unrelated to this problem
.alternateTypeRules(
// Rule to correctly process Optional<Instant> variables
// and generate "type: string, format: date-time", as for Instant variables,
// instead of "$ref" : "#/definitions/Instant"
AlternateTypeRules.newRule(
typeResolver.resolve(Optional.class, Instant.class),
typeResolver.resolve(Date.class),
Ordered.HIGHEST_PRECEDENCE
))
.genericModelSubstitutes(Optional.class)
.select()
//Some more code unrelated to this problem
.build();
}
}

With spring fox the problem is it doesn't use the custom ObjectMapper which you have defined as a Bean.
Springfox creates own ObjectMapper using new keyword. Hence, any module you register with your custom ObjectMapper is pointless for SpringFox. However, Springfox provides an interface to register modules with it's own ObjectMapper.
Create a configuration bean like below in your project and it should work.
#Configuration
public class ObjectMapperModuleRegistrar implements JacksonModuleRegistrar {
#Override
public void maybeRegisterModule(ObjectMapper objectMapper) {
objectMapper.registerModule(new ParameterNamesModule())
.registerModule(new Jdk8Module())
.registerModule(new JavaTimeModule())
.findAndRegisterModules();
}
}

Related

Spring Redis: Parsing Object from Redis Stream throws ConversionFailedException

I'm trying to parse objects from a Redis stream via Spring Boot Reactive Redis that are added by an external service. I'm using the following the tutorial to retrieve the elements from the stream via a StreamListener<String, ObjectRecord<String, TestDTO>>.
The object in the Redis stream consists of an id, a number and a Protobuf byte array (which is produced from a Python service via SerializeToString())
The Redis data retrieved via the redis-cli looks like this:
1) "1234567891011-0"
2) 1) "id"
2) "f63c2bcd...."
3) "number"
4) "5"
5) "raw_data"
6) "\b\x01\x12...
I've created the following DTO to match the objects in the Redis stream:
#Data
#NoArgsConstructor
public class TestDTO {
private UUID id;
private long number;
private byte[] raw_data;
}
However this throws the following error:
org.springframework.core.convert.ConversionFailedException: Failed to convert from type [org.springframework.data.redis.connection.stream.StreamRecords$ByteMapBackedRecord] to type [com.test.test.TestDTO] for value 'MapBackedRecord{recordId=1647417370847-0, kvMap={[B#2beed3c=[B#523baefb, [B#76cea664=[B#62358d82, [B#7ad95089=[B#35d4c48e}}'; nested exception is java.lang.IllegalArgumentException: Value must not be null!
Reading it a as generic MapRecord<String, String, String> works without any problem, but converting it directly to an Object would make for cleaner code. I have the feeling that I need to specify a deserializer, but I haven't found out yet, how to do that. Any recommendations on how to tackle this issue would be more than welcome!
You need to specify a RedisTemplate bean, where you can specify the Key/Value serialization/deserialization. In your case probably you should use GenericJackson2JsonRedisSerializer.
Example using StringRedisSerializer:
#Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
RedisTemplate javadoc: https://docs.spring.io/spring-data/redis/docs/current/api/org/springframework/data/redis/core/RedisTemplate.html
Spring Data Redis documentation: https://docs.spring.io/spring-data/redis/docs/current/reference/html/
Available serializers: https://docs.spring.io/spring-data/redis/docs/current/reference/html/#redis:serializer
This question can also help you: RedisTemplate hashvalue serializer to use for nested object with multiple types
It seems:
StreamListener<String, ObjectRecord<String, SomeDTO>>
is not available in spring-boot 3.
In my case I was using spring-boot version 3.0.0 and even upgraded to 3.0.1.
I noticed this same issue in these spring-boot versions. I fixed it by adding a spring-data-redis dependency and specifying the version as any of the older 2.x.x versions
Previous POM.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
New POM.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.7.7</version>
</dependency>

Kafka Avro serialize/deserialize into concrete type using schema registry failing

I can't seem to be able to consume messages in their concrete avro implementation, I get the following exception:
class org.apache.avro.generic.GenericData$Record cannot be cast to class my.package.MyConcreteClass
Here is the code (I use Spring Boot)
MyProducer.java
private final KafkaTemplate<String, MyConcreteClass> kafkaTemplate;
public PositionProducer(KafkaTemplate<String, MyConcreteClass> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
public void sendMessage(MyConcreteClass myConcreteClass) {
this.kafkaTemplate.send(topic, myConcreteClass);
}
MyConsumer.java
#KafkaListener(topics = "#{'${consumer.topic.name}'}", groupId = "#{'${spring.kafka.consumer.group-id}'}")
public void listen(MyConcreteClass incomingMsg) {
//handle
}
Note that if I change everything to GenericRecord, the deserialization works properly, so I know all config (not pasted) is configured correctly.
Also maybe important to note that I didn't register the schema myself, and instead let my client code do it for me.
Any ideas?
EDIT:
Config:
#Bean
public ConsumerFactory<String, MyConcreteClass> consumerFactory() {
Map<String, Object> props = new HashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "io.confluent.kafka.serializers.KafkaAvroDeserializer");
props.put(KafkaAvroDeserializerConfig.SCHEMA_REGISTRY_URL_CONFIG, schemaRegistryUrl);
return new DefaultKafkaConsumerFactory<>(props);
}
MyConcreteClass needs to extend SpecificRecord
You can use the Avro maven plugin to generate it from a schema
Then you must configure the serializer to know you want to use specific records
props.put(KafkaAvroDeserializerConfig.SPECIFIC_AVRO_READER_CONFIG, "true") ;
In addition to OneCricketeer's answer, I encountered another java.lang.ClassCastException after setting the specific avro reader config. It was nested exception is java.lang.ClassCastException: class my.package.Envelope cannot be cast to class my.package.Envelope (my.package.Envelope is in unnamed module of loader 'app'; my.package.Envelope is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader #3be312bd); It seems like spring boot devtools wrapped the class in it's reloader module causing jvm thought that's a different class.
I removed the spring boot devtools in pom and it finally worked as expected now.
<!-- Remove this from pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
I was getting blocked by the same issue as you. Thing is that the KafkaAvroDeserializer was deserializing the message as GenericData$Record so then spring kafka is searching the class annotated with #KafkaListener to have #KafkaHandler methods with a parameter of this type.
You'll need to add this property to your spring kafka configuration so the deserializer can return directly the SpecificRecord classes that you previously need to generate with the avro plugin:
spring:
kafka:
properties:
specific.avro.reader: true
Then your consumer may be like this
#KafkaListener(...)
public void consumeCreation(MyAvroGeneratedClass specificRecord) {
log.info("Consuming record: {}", specificRecord);
}
You need to customize your consumer configuration.
The ContentDeserializer needs to be an KafkaAvroDeserializer with a reference to your schema registry.

Swagger does not show / document my RESTful endpoints (JAX-RS, Spring-boot)

I have developed a RESTful web service in Java and Spring boot using Jax-RS and I would like to document it with Swagger. I have so far successfully managed to map the swagger-ui.html page on http:8080/localhost/<context>/swagger-ui.html. Unfortunately, my RESTful endpoints do not appear anywhere.
What I am using:
pom.xml
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
Swagger configuration class
#Configuration
#EnableSwagger2
public class SwaggerConfiguration
{
#Autowired
private TypeResolver typeResolver;
#Bean
public Docket api()
{
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("org.nick.java.webservice.services"))
.paths(PathSelectors.any())
.build()
.enable(true)
.apiInfo(getApiInfo())
.tags(
new Tag("My web service", "Methods for my RESTful service")
);
}
private ApiInfo getApiInfo() {
ApiInfo apiInfo = new ApiInfoBuilder()
.title("API Documentation")
.description("API")
.version("1.0")
.contact(new Contact("mycompany", "", "nickath#mycompany.com"))
.build();
return apiInfo;
}
an example of the JAX-RS endpoints
package org.nick.java.webservice.services;
#Path("/contextsapi")
#Consumes("application/json")
#Produces("application/json")
#Api(value = "Contexts API", produces = "application/json")
public interface ContextAPI {
#Path("/contexts/contexts")
#GET
#ApiOperation( value = "get contexts",
response = List.class)
List<Context> getContexts();
screenshot of the swagger-ui.html page
as you can see, no 'get contexts' method has been generated
Any idea what I am doing wrong?
======= UPDATE - SERVICE IMPLEMENTATION ========
package org.nick.java.webservice.services.impl;
#Service
#Api(value = "Contexts Api Impl", produces = "application/json", description = "desc")
#Path("/contextsapi")
public class ContextAPIImpl implements ContextAPI {
#Override
#GET
#ApiOperation( value = "get contexts", response = List.class)
public List<Context> getContexts(){
//code ommitted
}
}
Solved
Finally I managed to solve my problem using the Swagger2Feature following the example from here https://code.massoudafrashteh.com/spring-boot-cxf-jaxrs-hibernate-maven-swagger-ui/
Maven dependencies
<cxf.version>3.1.15</cxf.version>
<swagger-ui.version>3.9.2</swagger-ui.version>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxrs</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-service-description-swagger</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>swagger-ui</artifactId>
<version>${swagger-ui.version}</version>
</dependency>
CxfConfig.java
#Configuration
public class CxfConfig {
#Autowired
private Bus bus;
#Bean
public Server rxServer(){
final JAXRSServerFactoryBean endpoint = new JAXRSServerFactoryBean();
endpoint.setProvider(new JacksonJsonProvider());
endpoint.setBus(bus);
endpoint.setAddress("/swagger");
endpoint.setServiceBeans(Arrays.<Object>asList(contextAPI());
Swagger2Feature swagger2Feature = new Swagger2Feature();
endpoint.setFeatures(Arrays.asList(swagger2Feature));
return endpoint.create();
}
#Bean
public ContextAPI contextAPI(){
return new ContextAPIImpl();
}
Now the swagger documentation is available on http://localhost:8080///swagger/api-docs?url=//swagger/swagger.json
To customize the endpoint's UI check the manual here
Swagger suppose not to show documentation for any API client. It will generate documentation for your service if there is any with swagger annotations.
To be confirmed about this, try creating a Spring #service and annotate with swagger annotations. The doc will be generated if every other aspects are taken care of. Since you can see the UI, I would assume the dependencies are right.
The idea here is, your task is to document your service and swagger helps with that. It's not your responsibility to generate/publish documentation for API(s) that your service consumes. Since you don't maintain the service, it doesn't make sense to maintain the documentation as well.
When I used Rest client for the first time, I also got a bit perplexed about this. But if you really think about it, this is expected and makes sense.
I would suggest to use Swagger 2 i faced the same issue.
the issue is with the Docket you have implemented , correct regular expression can help.
Example :
#Configuration
#EnableSwagger2
public class SwaggerConfig {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
}
You can refer to the link above Setting up Swagger 2 Example
The Source code example is also from the above link.

Swagger REST API documentation with Spring Boot

I want to use Swagger 2.0 with my Spring Boot RESTful web service to generate documentation. I have searched quite a bit for an answer to this. Basically I have a Spring Boot project with a set of controllers and I want to document the API's. I have the following dependencies setup in my POM file.
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.5.0</version>
</dependency>
This is my Swagger configuration class with the #Configuration and #EnableSwagger2:
#Configuration
#EnableSwagger2
public class SwaggerConfig {
#Bean
public Docket api(){
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.regex("/api/.*"))
.build()
.apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("My application title")
.description("This is a test of documenting EST API's")
.version("V1.2")
.termsOfServiceUrl("http://terms-of-services.url")
.license("LICENSE")
.licenseUrl("http://url-to-license.com")
.build();
}
}
From what I have gathered in reading a couple of other answers here that at this point I should be able to see something at a URL such as http://myapp/v2/api-docs or alternatively http://localhost:8080/myapp/api-docs I have made the assumption that the "myapp" portion of the above URL refers to the name of the class in which my main resides (is this correct)? Also I have tried this with port 8080 and port 80 and the bottom line is that I see nothing other than site can't be reached. I have looked at the answers provided here and here however I'm not having any success. Any help would be much appreciated, thank you in advance.
As you can see on the following documentation :
https://springfox.github.io/springfox/docs/snapshot/#springfox-swagger-ui
The endpoint is now on swagger-ui.html, for your case, it will be http://localhost:8080/myapp/swagger-ui.html
I used, <artifactId>springdoc-openapi-ui</artifactId> with
public class OpenApiConfiguration{
#Bean
public GroupedOpenApi abcApp(){
String[] abcAppRootPath={"com.stockoverflow.swagger"};
return GroupedOpenApi.builder().group("my app").packagesToScan(abcAppRootPath).build();
}
}
reference : https://springdoc.org/#getting-started

How to configure Jackson in Wildfly?

I've got a Session Bean with the following method:
#POST
#Consumes("application/x-www-form-urlencoded")
#Path("/calculate")
#Produces("application/json")
public CalculationResult calculate(#FormParam("childProfile") String childProfile,
#FormParam("parentProfile") String parentProfile) {
...
}
The returned CalculationResult cannot be mapped to JSON and the following exception occurs:
Caused by: com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class com.test.UniqueName and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)...
How can I configure Jackson and its SerializationFeature in Wildfly?
"How can I configure Jackson and its SerializationFeature in Wildfly?"
You don't need to configure it in Wildfly, you can configure it in the JAX-RS applciation. Just use a ContextResolver to configure the ObjectMapper (see more here). Something like
#Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public ObjectMapperContextResolver() {
mapper = new ObjectMapper();
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
If you don't already have the Jackson dependency, you need that, just as a compile-time dependency
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson-provider</artifactId>
<version>3.0.8.Final</version>
<scope>provided</scope>
</dependency>
If you are using scanning to discover your resource classes and provider classes, the ContextResolver should be discovered automatically. If you explicitly registering all your resource and providers, then you'll need to register this one also. It should be registered as a singleton.
UPDATE
As #KozProv mentions in a comment, it should actually be resteasy-jackson2-provider as the artifactId for the Maven dependency. -jackson- uses the older org.codehaus (Jackson 1.x), while the -jackson2- uses the new com.fasterxml (Jackson 2.x). Wildfly by default uses The Jackson 2 version.
Wildfly 9
pom.xml
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson2-provider</artifactId>
<version>3.0.8.Final</version>
<scope>provided</scope>
</dependency>
Java class
#com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true)
public class SomePojo implements Serializable {
}

Categories