feign client not receiving ResponseEntity - spring-cloud

I am learning Feign (Spring 2.7.0 with cloud 2021.0.3) with my micro-services. The communication between services is working but I am unable to have Feign actually receive a ResponseEntity - only the DTO or List.
I have a service endpoint that returns a list in a ResponseEntity:
#RestController
#RequestMapping("/api")
#RequiredArgsConstructor
public class ApiController {
#GetMapping("/all")
ResponseEntity getAll() {
List<ApiKey> apiKeyList = apiService.getAll();
if ( apiKeyList == null || apiKeyList.size() < 1) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).contentType(MediaType.APPLICATION_JSON)
.body(new ErrorResponse("No APIKeys were found "));
} else {
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(apiKeyList);
}
The List contains multiple:
#Getter
#Setter
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class ApiKey {
#NotNull
private String apiKey;
private UUID uuid;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate beginDate;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate endDate;
The Feign client that actually makes the call is:
#FeignClient(name = "identity-service")
public interface IdentityServiceClient {
#RequestMapping(method = RequestMethod.GET, value = "/api/all")
public List<ApiKey> getAllApiKeys();
// the above works, the below errors out:
public ResponseEntity getAllApiKeys();
So the Feign client will work, as long as it expects List keyResponse
But I would like to have that in the ResponseEntity so that I can any potential status code.
How do I get feign client to work with ResponseEntity?

Related

How to retrieve nested rest data resources generated by #RepositoryRestResource annotation?

I have to entities exposed by spring boot application powered by Spring data REST.
#Entity
#Table(name = "joke")
#Data
public class Joke {
#Id
#Column(name = "joke_id")
private Long id;
#Column(name = "content")
private String content;
#JsonProperty("category")
#JoinColumn(name = "category_fk")
#ManyToOne(fetch = FetchType.EAGER)
private Category category;
}
and category
#Entity
#Table(name = "category")
#Data
public class Category {
#Id
#Column(name = "category_id")
private int id;
#Column(name = "name")
private String name;
}
It is working fine and exposing the HAL+Json format. I'm using Traverson client which is working fine:
Traverson client = new Traverson(URI.create("http://localhost:8080/api/"),
MediaTypes.HAL_JSON);
HashMap<String, Object> parameters = Maps.newHashMap();
parameters.put("size", "2");
PagedModel<JokesDTO> jokes = client
.follow("jokes")
.withTemplateParameters(parameters)
.toObject(new PagedModelType<JokesDTO>() {
});
return jokes;
where JokesDTO is:
#Builder(toBuilder = true)
#Value
#JsonDeserialize(builder = JokesDTO.JokesDTOBuilder.class)
#JsonInclude(Include.NON_NULL)
public class JokesDTO {
private String content;
#JsonPOJOBuilder(withPrefix = "")
#JsonIgnoreProperties(ignoreUnknown = true)
public static class JokesDTOBuilder {
}
}
I'm new in HAL and HateOS and I would like to achieve 2 things (and question is - is it possible, and how):
Base on Traverson client call, how to retrieve category (or link to category) in one call? How to extend what I wrote. And I'm not talking about adding additional #JsonProperty annotation to my class definition.
Is it possible to expose the inner query from Spring data REST, so I would be able to get all data with one call, is it possible with #RepositoryRestResource?

Serialize/Deserialize generic types in Spring Cloud Kafka Streams

The main purpose is to read a stream from a topic, apply some transformations and then send two events to other topics. For that we are using Kstream.branch() function and using functional style programming. The code is:
Input POJO:
#Data
#NoArgsConstructor
#AllArgsConstructor
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class FooInput {
#JsonProperty("field1")
private String field1;
#JsonProperty("field2")
private String field2;
}
Output POJO:
#Getter
#Setter
#ToString
#EqualsAndHashCode
public class FooEvent<T> extends EventInfo {
#JsonProperty(value = "entity")
private T entity;
#Builder
private FooEvent(T entity, String eventId, OffsetDateTime eventTime, Action eventAction, String eventSourceSystem, String eventEntityName) {
super(eventId, eventTime, eventAction, eventSourceSystem, eventEntityName);
this.entity = entity;
}
public FooEvent() {
super();
}
}
#Setter
#Getter
#ToString
#AllArgsConstructor
#NoArgsConstructor
public abstract class EventInfo {
#JsonProperty(value = "eventId")
private String eventId;
#JsonProperty(value = "eventTime")
private OffsetDateTime eventTime;
#JsonProperty(value = "eventAction")
private Action eventAction;
#JsonProperty(value = "eventSourceSystem")
private String eventSourceSystem;
#JsonProperty(value = "eventEntityName")
private String eventEntityName;
}
#Data
#NoArgsConstructor
#AllArgsConstructor
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Bar {
#JsonProperty("field1")
private String field1;
#JsonProperty("field2")
private String field2;
#JsonProperty("field3")
private String field3;
}
Processor function:
#Bean
public Function<KStream<String, FooInput>, KStream<String, FooEvent<Bar>>[]> process() {
Predicate<String, FooEvent<Bar>> predicate1=
(key, value) -> value.getEntity().getField1().equalsIgnoreCase("test1");
Predicate<String, FooEvent<Bar>> predicate2=
(key, value) -> value.getEntity().getField1().equalsIgnoreCase("test2");
return input -> {
input
...
.branch(predicate1, predicate2);
};
}
The binds are declared in appplication.properties:
Input:
spring.cloud.stream.bindings.process-in-0.destination=topic0
spring.cloud.stream.bindings.process-in-0.content-type=application/json
Output:
spring.cloud.stream.bindings.process-out-0.destination=topic1
spring.cloud.stream.bindings.process-out-0.content-type=application/json
spring.cloud.stream.bindings.process-out-1.destination=topic2
spring.cloud.stream.bindings.process-out-1.content-type=application/json
The issue is when the application evaluates the predicate. It appears that it tries to convert to FooEvent<Bar>. It converts the eventId, eventTime, eventAction, ... fields just fine but when it comes to the entity field (in this case Bar) it stores the values on a HashMap (instead of creating a new Bar object and setting the proper fields) which leads me to believe that Spring default Serde (JsonSerde) is doing something wrong. Any suggestions on how to solve generic types Serde problem in Kafka Streams?

RESTEasy Could not find MessageBodyWriter for response object of type: <Entity Class> of media type: application/json

I'm trying to implement a rest endpoint using rest easy. This is a GET endpoint,
#Controller
#Path("/api")
public class TestController {
private static final Log LOG = LogFactory.getLog(TestController .class);
#GET
#Path("/test")
#Produces(MediaType.APPLICATION_JSON)
public Response getTest() {
LOG.info(" inside test");
Response r = null;
try {
Test test = new Test();
test.setId(1L);
test.setName("test");
test.setAge("20");
r = Response.ok(test).build();
} catch (Exception e){
LOG.error(e);
}
return r;
}
}
Below is the entity class which I'm trying to return
#XmlRootElement
public class Test {
#XmlElement(name = "id")
private Long id;
#XmlElement(name = "name")
private String name;
#XmlElement(name = "age")
private String age;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
Getting below error when the endpoint is called from a rest client.
Could not find MessageBodyWriter for response object of type: com.package.Test of media type: application/json
These are some dependencies I have added which I believe would be useful for this.
httpclient-4.0.3.jar
httpcore-4.0.1.jar
jackson-core-asl-1.6.3.jar
jackson-jaxrs-1.9.13.jar
jackson-jaxrs-json-provider-2.2.1.jar
jackson-mapper-asl-1.6.3.jar
jackson-xc-1.6.3.jar
jaxrs-api-3.0.12.Final.jar
jboss-logging-3.3.1.Final.jar
jcip-annotations-1.0.jar
resteasy-jaxb-provider-3.1.0.Final.jar
resteasy-jettison-provider-2.3.1.GA.jar
resteasy-spring-2.2.1.GA.jar
scannotation-1.0.3.jar
Does anyone has an idea why this kind of error coming. endpoint is able return a plain string as a response.
jackson-jaxrs-json-provider contains the MessageBodyReader/Writer to handle JSON/POJO conversion. But you still need to register its JacksonJaxbJsonProvider with your application.
If you are using RESTEasy, you can also just add the resteasy-jackson-provider dependency to your project and it will automatically register the JacksonJaxbJsonProvider so you don't need to explicitly do it. The dependency also adds a few other useful items. So your best bet is to just add the dependency.

how to store PostgreSQL jsonb using SpringBoot + JPA?

I'm working on a migration software that will consume unknown data from REST services.
I already think about use MongoDB but I decide to not use it and use PostgreSQL.
After read this I'm trying to implement it in my SpringBoot app using Spring JPA but I don't know to map jsonb in my entity.
Tried this but understood nothing!
Here is where I am:
#Repository
#Transactional
public interface DnitRepository extends JpaRepository<Dnit, Long> {
#Query(value = "insert into dnit(id,data) VALUES (:id,:data)", nativeQuery = true)
void insertdata( #Param("id")Integer id,#Param("data") String data );
}
and ...
#RestController
public class TestController {
#Autowired
DnitRepository dnitRepository;
#RequestMapping(value = "/dnit", method = RequestMethod.GET)
public String testBig() {
dnitRepository.insertdata(2, someJsonDataAsString );
}
}
and the table:
CREATE TABLE public.dnit
(
id integer NOT NULL,
data jsonb,
CONSTRAINT dnit_pkey PRIMARY KEY (id)
)
How can I do this?
Note: I don't want/need an Entity to work on. My JSON will always be String but I need jsonb to query the DB
Tried this but understood nothing!
To fully work with jsonb in Spring Data JPA (Hibernate) project with Vlad Mihalcea's hibernate-types lib you should just do the following:
1) Add this lib to your project:
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>2.2.2</version>
</dependency>
2) Then use its types in your entities, for example:
#Data
#NoArgsConstructor
#Entity
#Table(name = "parents")
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
public class Parent implements Serializable {
#Id
#GeneratedValue(strategy = SEQUENCE)
private Integer id;
#Column(length = 32, nullable = false)
private String name;
#Type(type = "jsonb")
#Column(columnDefinition = "jsonb")
private List<Child> children;
#Type(type = "jsonb")
#Column(columnDefinition = "jsonb")
private Bio bio;
public Parent(String name, List children, Bio bio) {
this.name = name;
this.children = children;
this.bio = bio;
}
}
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Child implements Serializable {
private String name;
}
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Bio implements Serializable {
private String text;
}
Then you will be able to use, for example, a simple JpaRepository to work with your objects:
public interface ParentRepo extends JpaRepository<Parent, Integer> {
}
parentRepo.save(new Parent(
"parent1",
asList(new Child("child1"), new Child("child2")),
new Bio("bio1")
)
);
Parent result = parentRepo.findById(1);
List<Child> children = result.getChildren();
Bio bio = result.getBio();
You are making things overly complex by adding Spring Data JPA just to execute a simple insert statement. You aren't using any of the JPA features. Instead do the following
Replace spring-boot-starter-data-jpa with spring-boot-starter-jdbc
Remove your DnitRepository interface
Inject JdbcTemplate where you where injecting DnitRepository
Replace dnitRepository.insertdata(2, someJsonDataAsString ); with jdbcTemplate.executeUpdate("insert into dnit(id, data) VALUES (?,to_json(?))", id, data);
You were already using plain SQL (in a very convoluted way), if you need plain SQL (and don't have need for JPA) then just use SQL.
Ofcourse instead of directly injecting the JdbcTemplate into your controller you probably want to hide that logic/complexity in a repository or service.
There are already several answers and I am pretty sure they work for several cases. I don't wanted to use any more dependencies I don't know, so I look for another solution.
The important parts are the AttributeConverter it maps the jsonb from the db to your object and the other way around. So you have to annotate the property of the jsonb column in your entity with #Convert and link your AttributeConverter and add #Column(columnDefinition = "jsonb") as well, so JPA knows what type this is in the DB. This should already make it possible to start the spring boot application. But you will have issues, whenever you try to save() with the JpaRepository. I received the message:
PSQLException: ERROR: column "myColumn" is of type jsonb but
expression is of type character varying.
Hint: You will need to rewrite or cast the expression.
This happens because postgres takes the types a little to serious.
You can fix this by a change in your conifg:
datasource.hikari.data-source-properties: stringtype=unspecified
datasource.tomcat.connection-properties: stringtype=unspecified
Afterwards it worked for me like a charm, and here is a minimal example.
I use JpaRepositories:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface MyEntityRepository extends JpaRepository<MyEntity, Integer> {
}
The Entity:
import javax.persistence.Column;
import javax.persistence.Convert;
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
#Convert(converter = MyConverter.class)
#Column(columnDefinition = "jsonb")
private MyJsonObject jsonContent;
}
The model for the json:
public class MyJsonObject {
protected String name;
protected int age;
}
The converter, I use Gson here, but you can map it however you like:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
#Converter(autoApply = true)
public class MyConverter implements AttributeConverter<MyJsonObject, String> {
private final static Gson GSON = new Gson();
#Override
public String convertToDatabaseColumn(MyJsonObject mjo) {
return GSON.toJson(mjo);
}
#Override
public MyJsonObject convertToEntityAttribute(String dbData) {
return GSON.fromJson(dbData, MyJsonObject.class);
}
}
SQL:
create table my_entity
(
id serial primary key,
json_content jsonb
);
And my application.yml (application.properties)
datasource:
hikari:
data-source-properties: stringtype=unspecified
tomcat:
connection-properties: stringtype=unspecified
For this case, I use the above tailored converter class, you are free to add it in your library. It is working with the EclipseLink JPA Provider.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.log4j.Logger;
import org.postgresql.util.PGobject;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Map;
#Converter
public final class PgJsonbToMapConverter implements AttributeConverter<Map<String, ? extends Object>, PGobject> {
private static final Logger LOGGER = Logger.getLogger(PgJsonbToMapConverter.class);
private static final ObjectMapper MAPPER = new ObjectMapper();
#Override
public PGobject convertToDatabaseColumn(Map<String, ? extends Object> map) {
PGobject po = new PGobject();
po.setType("jsonb");
try {
po.setValue(map == null ? null : MAPPER.writeValueAsString(map));
} catch (SQLException | JsonProcessingException ex) {
LOGGER.error("Cannot convert JsonObject to PGobject.");
throw new IllegalStateException(ex);
}
return po;
}
#Override
public Map<String, ? extends Object> convertToEntityAttribute(PGobject dbData) {
if (dbData == null || dbData.getValue() == null) {
return null;
}
try {
return MAPPER.readValue(dbData.getValue(), new TypeReference<Map<String, Object>>() {
});
} catch (IOException ex) {
LOGGER.error("Cannot convert JsonObject to PGobject.");
return null;
}
}
}
Usage example, for an entity named Customer.
#Entity
#Table(schema = "web", name = "customer")
public class Customer implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Convert(converter = PgJsonbToMapConverter.class)
private Map<String, String> info;
public Customer() {
this.id = null;
this.info = null;
}
// Getters and setter omitted.
If you're using R2DBC you can use dependency io.r2dbc:r2dbc-postgresql, and use type io.r2dbc.postgresql.codec.Json in your member attributes of an entity class, e.g.:
public class Rule {
#Id
private String client_id;
private String username;
private String password;
private Json publish_acl;
private Json subscribe_acl;
}

need Spring Data JPA resource entry to make one many rest call

Using Spring Data jpa and Spring Data Rest I could able to get basic CRUD operations to work. But I am facing problem with one to many (owner -> car(s)) relationship. Can any one help me in this.
Owner.java
#Entity
#Table(name = "OWNER")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Owner implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "age")
private Integer age;
#OneToMany(mappedBy = "owner")
#JsonIgnore
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Car> cars = new HashSet<>();
}
OwnerResource.java
#RestController
#RequestMapping("/api")
public class OwnerResource {
private final Logger log = LoggerFactory.getLogger(OwnerResource.class);
#Inject
private OwnerRepository ownerRepository;
#RequestMapping(value = "/owners",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public ResponseEntity<Owner> create(#RequestBody Owner owner) throws URISyntaxException {
log.debug("REST request to save Owner : {}", owner);
if (owner.getId() != null) {
return ResponseEntity.badRequest().header("Failure", "A new owner cannot already have an ID").body(null);
}
Owner result = ownerRepository.save(owner);
return ResponseEntity.created(new URI("/api/owners/" + result.getId()))
.headers(HeaderUtil.createEntityCreationAlert("owner", result.getId().toString()))
.body(result);
}
#RequestMapping(value = "/owners",
method = RequestMethod.PUT,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public ResponseEntity<Owner> update(#RequestBody Owner owner) throws URISyntaxException {
log.debug("REST request to update Owner : {}", owner);
if (owner.getId() == null) {
return create(owner);
}
Owner result = ownerRepository.save(owner);
return ResponseEntity.ok()
.headers(HeaderUtil.createEntityUpdateAlert("owner", owner.getId().toString()))
.body(result);
}
#RequestMapping(value = "/owners",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public ResponseEntity<List<Owner>> getAll(#RequestParam(value = "page" , required = false) Integer offset,
#RequestParam(value = "per_page", required = false) Integer limit)
throws URISyntaxException {
Page<Owner> page = ownerRepository.findAll(PaginationUtil.generatePageRequest(offset, limit));
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/owners", offset, limit);
return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
}
#RequestMapping(value = "/owners/{id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public ResponseEntity<Owner> get(#PathVariable Long id) {
log.debug("REST request to get Owner : {}", id);
return Optional.ofNullable(ownerRepository.findOne(id))
.map(owner -> new ResponseEntity<>(
owner,
HttpStatus.OK))
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
}
OwnerRepository.java
/**
* Spring Data JPA repository for the Owner entity.
*/
public interface OwnerRepository extends JpaRepository<Owner,Long> {
}
The basic crud operation is working fine for Owner. But now I need to get all cars of a particular owner for that I need to add one rest call entry in OwnerResource.java and a method entry in OwneRepository.java. I tried different ways but getting many errors and is not working. The following is what I tried.
In OwnerRepository.java
Owner findAllByOwnerId(Long id);//But eclipse shows error here for this method
In OwnerResource.java
//Get All Cars
#RequestMapping(value = "/{id}/cars",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public ResponseEntity<Owner> getAll(#PathVariable Long id) {
log.debug("REST request to get All Cars of the Owner : {}", id);
return Optional.ofNullable(ownerRepository.findAllByOwnerId(id))
.map(owner -> new ResponseEntity<>(
owner,
HttpStatus.OK))
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
But these two changes are not working out. I am beginner to spring data jpa and spring data rest. Can any one help me in correcting these two so that I can get all cars of the owner.
I believe it shows an error because the findAll returns a different type of object: List, Page, etc...
Try this:
List<Owner> findAllByOwnerId(#Param("id") Long id);
That will return you a list of objects. If you want to return with pagination, than you need this instead:
Page<Owner> findAllByOwnerId(#Param("id") Long id, Pageable pageable);
I hope this helps, let me know how it works for you.