HATEOAS - links are added on every single request - spring

I have a rest API for class Tag, and want to add a link for each object.
but the duplicate link is added on every request(which calls the findAll() method).
see the pictures below
tag after first request
tag after many requests
What should I change to make it appear only once?
Model -
public class Tag extends RepresentationModel<Tag> {
...
}
Controller -
#RestController
#RequestMapping("tags")
public class TagController {
private final TagService tagService;
#Autowired
public TagController(TagService tagService) {
this.tagService = tagService;
}
#GetMapping()
public ResponseEntity<List<Tag>> findAll() {
List<Tag> tags = this.tagService.findAll();
List<Tag> response = new ArrayList<>();
for(Tag t : tags) {
t.add(linkTo(methodOn(TagController.class).find(t.getId())).withSelfRel());
response.add(t);
}
return new ResponseEntity<>(response, HttpStatus.OK);
}
#GetMapping("/{id}")
public ResponseEntity<Tag> find(#PathVariable("id") Long id) {
return ResponseEntity.ok(this.tagService.find(id));
}

Related

Is Spring #Component annotation used correctly?

The purpose of this question is to find out if the codes are written with the right approach. Let's do CRUD operations on categories and posts in the blog website project. To keep the question short, I shared just create and update side.
(Technologies used in the project: spring-boot, mongodb)
Let's start to model Category:
#Document("category")
public class Category{
#Id
private String id;
#Indexed(unique = true, background = true)
private String name;
#Indexed(unique = true, background = true)
private String slug;
// getter and setter
Abstract BaseController class and IController Interface is created for fundamental level save, delete and update operations. I shared below controller side:
public interface IController<T>{
#PostMapping("/save")
ResponseEntity<BlogResponse> save(T object);
#GetMapping(value = "/find-all")
ResponseEntity<BlogResponse> findAll();
#GetMapping(value = "/delete-all")
ResponseEntity<BlogResponse> deleteAll();
}
public abstract class BaseController<T extends MongoRepository<S,String>, S> implements IController<S> {
#Autowired
private T repository;
#Autowired
private BlogResponse blogResponse;
#PostMapping(value = "/save", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public #ResponseBody ResponseEntity<BlogResponse> save(S object) {
try {
S model = (S) repository.save(object);
String modelName = object.getClass().getSimpleName().toLowerCase();
blogResponse.setMessage(modelName + " is saved successfully").putData(modelName, object);
} catch (DuplicateKeyException dke) {
return new ResponseEntity<BlogResponse>(blogResponse.setMessage("This data is already existing!!!"), HttpStatus.BAD_REQUEST);
} catch (Exception e) {
return new ResponseEntity<BlogResponse>(blogResponse.setMessage(e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<BlogResponse>(blogResponse, HttpStatus.OK);
}
// delete, findAll and other controllers
#RestController
#RequestMapping(value = "category")
#RequestScope
public class CategoryController extends BaseController<ICategoryRepository, Category>{
// More specific opretions like findSlug() can be write here.
}
And finally BlogResponce component is shared below;
#Component
#Scope("prototype")
public class BlogResponse{
private String message;
private Map<String, Object> data;
public String getMessage() {
return message;
}
public BlogResponse setMessage(String message) {
this.message = message;
return this;
}
public BlogResponse putData(String key, Object object){
if(data == null)
data = new HashMap<String,Object>();
data.put(key,object);
return this;
}
public Map<String,Object> getData(){
return data;
}
#Override
public String toString() {
return "BlogResponse{" +
"message='" + message + '\'' +
", data=" + data +
'}';
}
}
Question: I am new spring boot and I want to move forward by doing it right. BlogResponse is set bean by using #Component annotation. This doc said that other annotations like #Controller, #Service are specializations of #Component for more specific use cases. So I think, I cant use them. BlogResponse is set prototype scope for create new object at each injection. Also it's life end after response because of #RequestScope. Are this annotations using correcty? Maybe there is more effective way or approach. You can remark about other roughness if it existing.

Get all documents from an index using spring-data-elasticsearch

I am trying to connect to my external ElasticSearch server with Spring Boot.
If I do a curl from command line, I get expected results.
curl "http://ipAddr:9200/indexName/TYPE/_search?pretty=true"
But getting this error when I try to access it via Spring Boot.
<html><body><h1>Whitelabel Error Page</h1><p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p><div id='created'>Mon Sep 11 12:39:15 IST 2017</div><div>There was an unexpected error (type=Internal Server Error, status=500).</div><div>Could not write JSON: (was java.lang.NullPointerException); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl["facets"])</div></body></html>
Not sure why a NullPointerException and what is aggregartion.impl
Here is my Spring Application:
Controller:
#RestController
public class PojoController {
#Autowired
PojoService pojoService;
#RequestMapping(value = "/", method=RequestMethod.GET)
public #ResponseBody String index() {
return new String("Welcome:)");
}
#RequestMapping(value = "/all", method = RequestMethod.GET,
produces = { MediaType.APPLICATION_JSON_VALUE })
#ResponseBody List<POJO> findAll() {
try {
List<POJO> pojoObj = pojoService.findAll();
return pojoObj;
} catch (Exception exp) {
exp.printStackTrace();
return null;
}
}
}
Repository:
#Repository
public interface PojoRepository extends ElasticsearchRepository<POJO, Integer> {
List<POJO> findAll();
}
Service:
#Service
public class POJOServiceImpl implements POJOService{
private POJORepository pojoRepository;
private ElasticsearchTemplate elasticsearchTemplate;
#Autowired
public void setPojoRepository(PojoRepository pojoRepository) {
this.pojoRepository = pojoRepository;
}
public POJO findOne(String id) {
return pojoRepository.findOne(id);
}
public List<POJO> findAll() {
return (List<POJO>) pojoRepository.findAll();
}
}
POJO class:
#Document(indexName = "INDEX", type = "TYPE")
public class POJO {
#Id
private Integer id;
private String name;
public POJO(){
// empty
}
public POJO(Integerid, String name) {
super();
this.id = id;
this.name = name;
}
// getters and setters
}
I should be able to query all the documents in the index. Later on, I will try and use filters etc.
Any help is appreciated. Thanks :)
It looks like Jackson has a problem with handling your POJO (probably related to this issue: DATAES-274) - the problematic part is casting in repository from Iterable collection to List.
Update
In case of repositories, spring-data-elasticsearch behaves a bit different than you would expect. Taking your example:
#Repository
public interface PojoRepository extends ElasticsearchRepository<POJO, Integer> {
List<POJO> findAll();
}
and after calling in your rest controller:
List<POJO> pojoObj = pojoService.findAll();
in debugger you will see something like this:
You would expect that pojoObj list contains objects of POJO class.
And here comes the surprise - pojoObj ArrayList contains one object of AggregatedPageImpl type and its content field is the right list that contains your POJO objects.
This is the reason why you get:
Could not write JSON: ... java.util.ArrayList[0]->org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl[\"facets\"])
As I wrote before, Jackson cannot handle this while serializing POJO objects.
Solution 1
Let repositories return Iterable collection (by default).
#Repository
public interface PojoRepository extends ElasticsearchRepository<POJO, Integer> {
}
Move the conversion part to the service but use some utility method (here with Guava) in order to have it like this:
import com.google.common.collect.Lists;
public List<POJO> findAll() {
return Lists.newArrayList(pojoRepository.findAll());
}
Solution 2
Use Page in repository (here simplified version without parameters):
#Repository
public interface PojoRepository extends ElasticsearchRepository<POJO, Integer> {
Page<TestDto> findAll();
}
If you still want to operate on list - get content from page in service:
public List<POJO> findAll() {
return testDtoRepository.findAll().getContent();
}

How add a new _link to an entity using spring data rest?

I want to add an extra link to a entity such as:
"_links": {
"self": {
"href": "http://localhost:8080/api/organizaciones"
},
"profile": {
"href": "http://localhost:8080/api/profile/organizaciones"
},
"search": {
"href": "http://localhost:8080/api/organizaciones/search"
},
"disable": {
"href": "http://localhost:8080/api/organizaciones/disable"
}
}
The idea behind this scenario is that I need to expose a soft delete via its own link within Organizacion entity... right now I'm only able to do:
http://localhost:8080/api/organizaciones/search/disable?id=100
in order to perform the soft delete. How can I achieve this the right way? Or is it my only alternative creating a controller?
You just need to add a class extending the ResourceProcessor interface and add it to the spring-context(http://docs.spring.io/spring-data/rest/docs/current/reference/html/#_the_resourceprocessor_interface)
For example
#Bean
public ResourceProcessor<Resource<Person>> personProcessor() {
return new ResourceProcessor<Resource<Person>>() {
#Override
public Resource<Person> process(Resource<Person> resource) {
resource.add(new Link("http://localhost:8080/people", "added-link"));
return resource;
}
};
}
Where the Person entity can be replaced with your Organizacion entity.
I finally figured it out, I did what was mentioned by Alex in a comment.
I have to give credit to the father of spring-data-rest to #olivergieke I checked one of his examples, more precise: restbucks
First created the following component
#Component
#RequiredArgsConstructor
public class OrganizacionResourceProcessor implements ResourceProcessor<Resource<Organizacion>>{
private static final String DISABLE_REL = "deshabilitar";
private static final String ENABLE_REL = "habilitar";
private final #NonNull EntityLinks entityLinks;
#Override
public Resource<Organizacion> process(Resource<Organizacion> resource) {
Organizacion organizacion = resource.getContent();
if(organizacion.isEnabled()){
resource.add(entityLinks.linkForSingleResource(Organizacion.class, organizacion.getId()).slash(DISABLE_REL).withRel(DISABLE_REL));
}
if(organizacion.isDisabled()){
resource.add(entityLinks.linkForSingleResource(Organizacion.class, organizacion.getId()).slash(ENABLE_REL).withRel(ENABLE_REL));
}
return resource;
}
}
Then created the controller to support those two operations...
#RepositoryRestController
#RequestMapping("/organizaciones")
#ExposesResourceFor(Organizacion.class)
#RequiredArgsConstructor
#Slf4j
#Transactional
public class OrganizacionController {
private final #NonNull OrganizacionRepository organizacionRepository;
private final #NonNull EntityLinks entityLinks;
#GetMapping(value="/{id}/habilitar")
public ResponseEntity<?> desactivarOrganizacion(#PathVariable("id") Long id) {
Preconditions.checkNotNull(id);
Organizacion organizacion = organizacionRepository.findOne(id);
if(organizacion == null){
return new ResponseEntity<Void>(HttpStatus.NOT_FOUND);
}
organizacion.setEstado(Estado.DESHABILITADO);
Organizacion pOrg = this.organizacionRepository.save(organizacion);
HttpHeaders header = new HttpHeaders();
header.setLocation(this.entityLinks.linkForSingleResource(Organizacion.class, pOrg.getId()).toUri());//construimos el URL
return new ResponseEntity<Void>(header,HttpStatus.CREATED);
}
#GetMapping(value="/{id}/deshabilitar")
public ResponseEntity<?> activarOrganizacion(#PathVariable("id") Long id){
Preconditions.checkNotNull(id);
Organizacion organizacion = organizacionRepository.findOne(id);
if(organizacion == null){
return new ResponseEntity<Void>(HttpStatus.NOT_FOUND);
}
organizacion.setEstado(Estado.ACTIVO);
Organizacion pOrg = this.organizacionRepository.save(organizacion);
HttpHeaders header = new HttpHeaders();
header.setLocation(this.entityLinks.linkForSingleResource(Organizacion.class, pOrg.getId()).toUri());//construimos el URL
return new ResponseEntity<Void>(header,HttpStatus.CREATED);
}
}
and that was it.
This was originally added to Revision 3 of the question.

Spring RestController ignore XmlElement annotation in Wrapper Class

I'm using Spring 4.x and I have following RestController method which should return list of all flights
#RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}, method = RequestMethod.GET)
public FlightWrapper returnAllFlights() {
List<FlightDto> flights = data.findAll();
return new FlightWrapper(flights);
}
FlightWrapper class looks like this (rootElement = flights element = flight):
#XmlRootElement(name = "flights")
public class FlightWrapper {
private List<FlightDto> flights;
public FlightWrapper() {}
public FlightWrapper(List<FlightDto> flights) {
this.flights = flights;
}
#XmlElement(name = "flight")
public List<FlightDto> getFlights() {
return flights;
}
public void setFlights(List<FlightDto> flights) {
this.flights = flights;
}
}
The problem is when I call returnAllFlights() it will return xml in this format:
<FlightWrapper>
<flights>
<flights>
....
</flights>
<flights>
....
</flights>
</flights>
</FlightWrapper>
I expected that single flight should have tag flight and whole list of flights should be flights however as you can see items in list have the same tag as list itself.
Any idea how to fix it ?
According with your comments since you are using jackson-dataformat-xml module the JAXB annotations now are ignored. You must update your class to use these annotations.
#JacksonXmlRootElement(localName="flights")
public class FlightWrapper {
private List<FlightDto> flights;
public FlightWrapper() {}
public FlightWrapper(List<FlightDto> flights) {
this.flights = flights;
}
#JacksonXmlElementWrapper(useWrapping=false)
#JacksonXmlProperty(localName="flight")
public List<FlightDto> getFlights() {
return flights;
}
public void setFlights(List<FlightDto> flights) {
this.flights = flights;
}
}
I had the same problem than you but through Spring Framework, not through Spring Boot. But that behaviour happens when the jackson-dataformat-xml module is added into the classpath. It according with my experience.

smartgwt listgrid RestDataSource not populating

Im new using this front end framework application...
I recently started to work with smartgwt and i'm bulding a new application with a Spring MVC integration.
I'm using a ListGrid with a RestDataSource (Consume the Rest service with mvc:annotation-driven for plain JSON)
I can see that the servaice gets consuming properly perhaps my grid is never shown with the data in it.
Can someone help me here ?
Here's my ListGrid class
public class ListGrid extends com.smartgwt.client.widgets.grid.ListGrid {
private final SpringJSONDataSource springJSONDataSource;
public ListGrid(List<DataSourceField> fields) {
this(new PatientDataSource(fields));
}
public ListGrid(SpringJSONDataSource springJSONDataSource) {
this.springJSONDataSource = springJSONDataSource;
init();
}
private void init() {
setAutoFetchData(true);
setAlternateRecordStyles(true);
setEmptyCellValue("???");
setDataPageSize(50);
setDataSource(springJSONDataSource);
}
}
Now there's the DataSource implmentation
public abstract class SpringJSONDataSource extends RestDataSource {
protected final HTTPMethod httpMethod;
public SpringJSONDataSource(List<DataSourceField> fields) {
this(fields, HTTPMethod.POST);
}
public SpringJSONDataSource(List<DataSourceField> fields, HTTPMethod httpMethod) {
this.httpMethod = httpMethod;
setDataFormat(DSDataFormat.JSON);
addDataSourceFields(fields);
setOperationBindings(getFetch());
addURLs();
}
private void addURLs() {
if(getUpdateDataURL() != null)
setUpdateDataURL(getUpdateDataURL());
if(getRemoveDataURL() != null)
setRemoveDataURL(getRemoveDataURL());
if(getAddDataURL() != null)
setAddDataURL(getAddDataURL());
if(getFetchDataURL() != null)
setFetchDataURL(getFetchDataURL());
}
private void addDataSourceFields(List<DataSourceField> fields) {
for (DataSourceField dataSourceField : fields) {
addField(dataSourceField);
}
}
protected abstract OperationBinding getFetch();
protected abstract OperationBinding getRemove();
protected abstract OperationBinding getAdd();
protected abstract OperationBinding getUpdate();
public abstract String getUpdateDataURL();
public abstract String getRemoveDataURL();
public abstract String getAddDataURL();
public abstract String getFetchDataURL();
}
The class PatientDataSource that extends SpringJSONDataSource
public class PatientDataSource extends SpringJSONDataSource {
public PatientDataSource(List<DataSourceField> fields) {
super(fields);
setPrettyPrintJSON(true);
}
#Override
protected OperationBinding getFetch() {
OperationBinding fetch = new OperationBinding();
fetch.setOperationType(DSOperationType.FETCH);
fetch.setDataProtocol(DSProtocol.POSTMESSAGE);
DSRequest fetchProps = new DSRequest();
fetchProps.setHttpMethod(httpMethod.toString());
fetch.setRequestProperties(fetchProps);
return fetch;
}
#Override
public String getFetchDataURL() {
return "/spring/fetchPatients";
}
#Override
protected OperationBinding getRemove() {
return null;
}
#Override
public String getRemoveDataURL() {
return null;
}
#Override
protected OperationBinding getAdd() {
return null;
}
#Override
public String getAddDataURL() {
return null;
}
#Override
protected OperationBinding getUpdate() {
return null;
}
#Override
public String getUpdateDataURL() {
return null;
}
}
My spring controller PatientControler
#Controller
public class PatienController {
Logger logger = Logger.getLogger(PatienController.class);
#Autowired
private PatientServices patientServices;
#RequestMapping(value = "/patientTest", method = RequestMethod.GET)
#ResponseBody
public Object getTest()
{
return patientServices.getAllPatients();
}
#RequestMapping(value = "/fetchPatients", method = RequestMethod.POST)
#ResponseBody
public Object getAllPatients()
{
return patientServices.getAllPatients();
}
}
PatientServiceImpl
public class PatientServicesImpl implements PatientServices {
public List<Patient> getAllPatients() {
List<Patient> patients = new ArrayList<Patient>();
Patient patient;
for(int i = 0 ; i < 500 ; i++){
patient = new Patient();
patient.setDateOfBirth(new Date());
patient.setFirstName("Joe");
patient.setMiddleName("Moe");
patient.setLastName("Blow");
patient.setLastConsultation(new Date());
patient.setSex(Sex.M);
patients.add(patient);
}
return patients;
}
}
*Im Really stuck right now i've been looking for all type of answers .... but so far nothing worked when i tried to override the transformResponse from my RestDataSource impentation the parameter "data" as an OBJECT, returns me an array [object Object],[object Object],[object Object],[object Object],[object Object] *
The Data which is transferred from the RestDataSource has a specific format which is described in the JavaDoc of the RestDataSource
Your server must understand the request and send back a valid response.
At the moment your example doesn't seem to honour the contract.
To debug the traffic send to and from your server you can use the SmartClient-Console. You can open it by a browser bookmark like this:
javascript:isc.showConsole()
Of cause you need to deploy this console by adding the following module to your gwt.xml
<inherits name="com.smartclient.tools.SmartClientTools"/>
Now go to the RPC Tab and check Track-RPCs

Resources