Swagger Request custom class as Number - jax-rs

I have extended the BigDecimal class as a Premium class, because I want to add some default behavior (i.e. the MathContext). In the created swagger.json a BigDecimal is presented as:
"schema": {
"type": "number"
}
while the Premium class is presented as:
"schema": {
"$ref": "#/definitions/Premium"
}
and:
"definitions": {
"Premium": {
"type": "object"
}
}
How can I annotate the Premium class so that it is represented in the swagger.json as a "type": "number"?
The Premium class looks as follows:
public class Premium extends BigDecimal {
private static final long serialVersionUID = 1L;
private static final MathContext DEFAULT_MC = MathContext.DECIMAL64;
public Premium(String val) {
super(val);
}
#JsonCreator
public Premium(#JsonProperty BigDecimal bd) {
this(bd.toString());
}
public Premium multiply(Premium val) {
return new Premium(super.multiply(val));
}
public Premium divide(Premium val) {
return new Premium(super.divide(val, DEFAULT_MC));
}
public Premium subtract(Premium val) {
return new Premium(super.subtract(val));
}
}
And I created a REST service that looks as follows:
#Path("/test")
#Api
public class PremiumSvc {
#POST
#Path("/pr")
#ApiOperation("Premium.")
public Premium doPr(Premium req) {
return req.subtract(new Premium("0.02"));
}
#POST
#Path("/bd")
#ApiOperation("BigDecimal.")
public BigDecimal doBd(BigDecimal req) {
return req.subtract(new BigDecimal("0.02"));
}
}

Related

How to return ObjectNode from Controller in Micronaut?

I have following code
#Controller()
public class TestController {
#Get(value = "test", produces = MediaType.APPLICATION_JSON)
public MyDto fetch() throws Exception {
return new MyDto(
"test",
new ObjectMapper().readValue("{\"a\": 1}", ObjectNode.class)
);
}
#Serializable
#Data
public static class MyDto {
private final String name;
private final ObjectNode extraFields;
public MyDto(String name, ObjectNode extraFields) {
this.name = name;
this.extraFields = extraFields;
}
}
}
And I have an unexpected output on the client, extraFields object is empty
{
"name": "test",
"extraFields": [
[]
]
}
How to make Micronaut controller properly serialize com.fasterxml.jackson.databind.node.ObjectNode ?

Join the result of several microservice in a Mono

I try to develop a api with spring-cloud-gateway but it's not easy for me
My "microservice A" return an Mono. This Object contains a List of id "Object B"
My "microservice B" return an Flux
In my api-gateway, how can i aggregate in a Mono the result of the microservices ?
#Service
public class ServiceAClient {
private final WebClient webClient;
public ServiceAClient(WebClient.Builder builder) {
this.webClient = builder.baseUrl("lb://microservice-A/A/").build();
}
public Mono<ObjectA> getObjectA(String id){
return webClient
.get()
.uri("{id}" , id)
.retrieve()
.bodyToMono(ObjectA.class)
.onErrorResume(ex->Mono.empty());
}
}
#Service
public class ServiceBClient {
private final WebClient webClient;
public ServiceAClient(WebClient.Builder builder) {
this.webClient = builder.baseUrl("lb://microservice-B/B/").build();
}
public Flux<ObjectB> getListObjectB(List<Long> ids){
return webClient
.get()
.uri("{ids}" , ids)
.retrieve()
.bodyToFlux(ObjectB.class);
}
}
#Data
public class ObjectA {
private UUID id;
private String name;
private String description;
private Date start;
private Date end;
private List<Long> listIdObjectB;
}
#Data
public class ObjectB {
private Long id;
private String name;
private String localisation;
}
#Data
public class MyDto {
private UUID id;
private String name;
private String description;
private Date start;
private Date end;
private List<ObjectB> listObjectB;
}
#Service
#AllArgsConstructor
public class CombinedService {
private final ServiceAClient serviceAClient;
private final ServiceBClient serviceBClient;
public Mono<MyDto> getDetails(String id){
// return MyDto who join a Mono Service A an Flux service B
}
}
The desired result
{
"id": "2355e7eb-edf7-428c-b51b-ac06c146ed3c",
"name": "toto",
"description": "lorem ipsum",
"debut": 01/06/2022,
"fin": 10/06/2022,
"ListObjectB": [
{
"id": 1,
"name": "foo",
"localisation": "here"
},
{
"id": 2,
"name": "bar",
"localisation": "here"
}
]
}
Thank you in advance for your help
You can take it as a start point to understand a possible solution.
I highly recommend you to read about reactive streams(https://www.reactive-streams.org/) and spring-webflux(https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html).
Your CombinedService should looks like:
#Service
#AllArgsConstructor
public class CombinedService {
private final ServiceAClient serviceAClient;
private final ServiceBClient serviceBClient;
public Mono<MyDto> getDetails(String id) {
return serviceAClient.getObjectA(id)
.map(objectA -> {
final Flux<ObjectB> fluxB = serviceBClient.getListObjectB(objectA.getListIdObjectB());
final List<ObjectB> listObjectB = fluxB.toStream().collect(Collectors.toList());
final MyDto myDto = new MyDto();
myDto.setName(objectA.getDescription());
myDto.setDescription(objectA.getDescription());
myDto.setListObjectB(listObjectB);
//More setters, etc
return myDto;
});
}
}

How to exclude Weld metadata from JSON object serialization

Assume the following REST resource:
#Path("/ActiveLeadTask")
//Also possible MediaType.APPLICATION_XML
#Produces(MediaType.APPLICATION_JSON)
public class ActiveLeadTask {
private #Inject ActiveLeadTaskBo activeLeadBo;
#GET
#Path("/getBo")
public ActiveLeadTaskBo getBo() {
return activeLeadBo;
}
}
////////////////////////////////////////////////////
#XmlRootElement
#XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
public class ActiveLeadTaskBo implements Serializable {
private static final long serialVersionUID = 1L;
private String firstName;
private String lastName;
private String phoneNumber;
private String phoneCountryCode;
private AtomicInteger accessCounterField = new AtomicInteger(0);
public ActiveLeadTaskBo() {
firstName = "test";
lastName = "test";
}
public int getAccessCounter() {
return accessCounterField.incrementAndGet();
}
public void setAccessCounter(int seed) {
accessCounterField.set(seed);
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
The REST response for getBo() JSON (but not for XML) returns the following:
{
"accessCounter": 1,
"firstName": "test",
"lastName": "test",
"metadata": {
"contextualInstance": {
"accessCounter": 2,
"firstName": "test",
"lastName": "test"
},
"instance": {
"accessCounter": 3,
"firstName": "test",
"lastName": "test"
}
}
}
The JSON response contains an additional "metadata" field - how can I configure the project to not generate it, or avoid generating it? The CDI container is Weld and the JSON serializer is provided by Yasson.
Two solutions are possible:
create a wrapper class, for example, ActiveLeadTaskBoInjectWrapper:
#Inject
ActiveLeadTaskBoInjectWrapper activeLeadBo;
activeLeadBo.getInstance();
workaround Weld specifics:
#Inject
Foo foo;
public void doSomething() {
if (foo instanceof WeldClientProxy) {
// foo is a proxy
((WeldClientProxy)foo).getMetadata().getContextualInstance()
} else {
// not a proxy
}
}
Depends on what JSON processing framework is used in your REST endpoint. For jsonb-api (jsr367) the only possible solution is to implement javax.json.bind.config.PropertyVisibilityStrategy to exclude generated properties from processing.

How to simply enable HAL+JSON object mapping with Spring Restemplate

I have been pulling my hair out trying to figure out how to simply enable this sort of functionality in my client in response to a HAL+JSON request. If I have the specific resource I can get the properties to bind but really would like the hrefs in an easy to use format so I can lazy fetch them.
Organization[] orgs = restTemplate.getForObject("http://myservice/organizations",Organizations[].class);
or
Organization org = restTemplate.getForObject("http://myservice/organizations/1",Organization.class);
Given the following HAL and entities:
{
"_embedded": {
"af:organizations": [
{
"name": "First Company",
"description": "Some company",
"_links": {
"self": {
"href": "http://localhost:8080/hal/organizations/1"
},
"af:workers": {
"href": "http://localhost:8080/hal/organizations/1/workers",
"title": "Cancel an order"
}
}
},
{
"name": "Second Company",
"description": "Someplace we all used to work",
"_links": {
"self": {
"href": "http://localhost:8080/hal/organizations/2"
},
"af:workers": {
"href": "http://localhost:8080/hal/organizations/2/workers",
"title": "All the little ants on your farm"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/hal/organizations"
},
"profile": {
"href": "http://localhost:8080/hal/profile/organizations"
},
"curies": [
{
"href": "/custom/docs/{rel}.txt",
"name": "af",
"templated": true
}
]
}
}
entity
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.List;
#Getter
#NoArgsConstructor
#EqualsAndHashCode
#AllArgsConstructor
public class Organization {
private String name;
private String description;
#JsonProperty("_links")
private Map<String, Link> links;
}
Configuration (which is my 3rd attempt. It's just my current one)
#Configuration
#EnablePluginRegistries(RelProvider.class)
#PropertySource("classpath:ant-farm-client.properties")
public class AntFarmClientConfig {
#Value("${server.url}")
private String base;
private static final boolean EVO_PRESENT =
ClassUtils.isPresent("org.atteo.evo.inflector.English", null);
#Autowired
private PluginRegistry<RelProvider, Class<?>> relProviderRegistry;
#Bean
public ObjectMapper jacksonObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return objectMapper;
}
#Bean
public MappingJackson2HttpMessageConverter jsonConverter() {
MappingJackson2HttpMessageConverter jacksonConverter = new
MappingJackson2HttpMessageConverter();
jacksonConverter.setSupportedMediaTypes(Arrays.asList(MediaType.valueOf("application/json")));
jacksonConverter.setObjectMapper(jacksonObjectMapper());
return jacksonConverter;
}
#Bean
public CurieProvider curieProvider() {
return new DefaultCurieProvider("af", new UriTemplate("http://schema.org/{rel}"));
}
#Bean
MessageSourceAccessor accessor(ApplicationContext context) {
return new MessageSourceAccessor(context);
}
#Bean
public RestOperations template(ObjectMapper mapper,MappingJackson2HttpMessageConverter halConverter ) {
RestTemplate restTemplate = new RestTemplate();
DefaultUriTemplateHandler handler = new DefaultUriTemplateHandler();
handler.setBaseUrl(base);
restTemplate.setUriTemplateHandler(handler);
restTemplate.getMessageConverters().add(halConverter);
return restTemplate;
}
#Bean
public MappingJackson2HttpMessageConverter halConverter(MessageSourceAccessor accessor) {
CurieProvider curieProvider = curieProvider();
RelProvider relProvider = new DelegatingRelProvider(relProviderRegistry);
ObjectMapper halObjectMapper = new ObjectMapper();
halObjectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
halObjectMapper.registerModule(new Jackson2HalModule());
halObjectMapper.setHandlerInstantiator(new
Jackson2HalModule.HalHandlerInstantiator(relProvider, curieProvider,accessor));
MappingJackson2HttpMessageConverter halConverter = new MappingJackson2HttpMessageConverter();
halConverter.setSupportedMediaTypes(Arrays.asList(MediaTypes.HAL_JSON));
halConverter.setObjectMapper(halObjectMapper);
return halConverter;
}
#Bean
RelProvider defaultRelProvider() {
return EVO_PRESENT ? new EvoInflectorRelProvider() : new DefaultRelProvider();
}
#Bean
RelProvider annotationRelProvider() {
return new AnnotationRelProvider();
}
I would try to get a Resources<Resource<Organization>> out of the RestTemplate. The outer Resources would contain the global links and each Resource in the content contains the item links then.
I would not create my own ObjectMapper - spring hateoas provides one. It is just important that your RestTemplate has the HttpMessageConverter in place that can convert application/hal+json.
This article shows an example for doing this https://dzone.com/articles/spring-resttemplate-linked
Currently I cannot try this myself so I can just provide you with these untested thoughts.

FasterXML schema generation for Maps and Object references

I have an issue generating a JSON Schema file with FasterXML.
The file output just shows
object type for a Map<String, String>
null type for OtherBean
{
"type": "object",
"properties": {
"beanId": {
"type": "integer"
},
"beanName": {
"type": "string"
},
"beanMap": {
"type": "object"
},
"otherBean": null
} }
My Schema generation class
import java.io.File;
import java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsonschema.JsonSchema;
public class Main {
public static void main(String[] args) throws IOException {
ObjectMapper MAPPER = new ObjectMapper();
JsonSchema jsonSchema = MAPPER.generateJsonSchema(MyBean.class);
MAPPER.writeValue(new File("MyBeanSchema.json"), jsonSchema);
}
}
MyBeans:
import java.util.Map;
public class MyBean {
private Integer beanId;
private String beanName;
private Map<String, String> beanMap;
private OtherBean otherBean;
public MyBean() {
}
public Integer getBeanId() {
return beanId;
}
public void setBeanId(Integer beanId) {
this.beanId = beanId;
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public Map<String, String> getBeanMap() {
return beanMap;
}
public void setBeanMap(Map<String, String> beanMap) {
this.beanMap = beanMap;
}
public OtherBean getOtherBean() {
return otherBean;
}
public void setOtherBean(OtherBean otherBean) {
this.otherBean = otherBean;
}
}
OtherBean:
public class OtherBean {
}
Not directly answering your question, but Schema Generation is moving to a separate module:
https://github.com/FasterXML/jackson-module-jsonSchema/
which will have better functionality, and can evolve faster than old in-built generation.
So if possible, try using that. And then you can file bugs against this, for problems with generation.