How to use HATEOAS with a custom page - java

I'm using spring boot to create a web service.
I'm trying to introduce HATEOAS to an endpoint. Here's the model(DTO):
public class MovieResponse {
private Long id;
private String name;
private Date releaseDate;
private Time runtime;
private Float rating;
private String storyline;
private String poster;
private String rated;
private Date createdAt;
private List<GenreResponse> genres = new ArrayList<>();
private List<MovieMediaResponse> videos = new ArrayList<>();
private List<MovieMediaResponse> photos = new ArrayList<>();
private List<MovieReviewResponse> reviews = new ArrayList<>();
private List<MovieCelebrityResponse> cast = new ArrayList<>();
private List<MovieCelebrityResponse> writers = new ArrayList<>();
private List<MovieCelebrityResponse> directors = new ArrayList<>();
// Getters & Setters
}
Also i have a PagedResponse which holds the page information:
public class PagedResponse<T> {
private List<T> content;
private int page;
private int size;
private long totalElements;
private int totalPages;
private boolean last;
public PagedResponse() {}
public PagedResponse(List<T> content, int page, int size, long totalElements, int totalPages, boolean last) {
this.content = content;
this.page = page;
this.size = size;
this.totalElements = totalElements;
this.totalPages = totalPages;
this.last = last;
}
// Getters & Setters
}
I also have getAllMovies method in the service which return PagedResponse<MovieResponse>.
Now here's MovieResourceAssembler
#Component
public class MovieResourceAssembler implements ResourceAssembler<MovieResponse, Resource<MovieResponse>> {
private EntityLinks entityLinks;
#Autowired
public void setEntityLinks(EntityLinks entityLinks) {
this.entityLinks = entityLinks;
}
#Override
public Resource<MovieResponse> toResource(MovieResponse movieResponse) {
Link self = entityLinks.linkFor(MovieResponse.class).slash(movieResponse.getId()).withSelfRel();
Link rel = entityLinks.linkFor(MovieResponse.class).slash(movieResponse.getId()).withRel("movie");
Link movieGenres = entityLinks.linkFor(MovieResponse.class).slash(movieResponse.getId()).slash("movieGenres").withRel("movieGenres");
Link movieReviews = entityLinks.linkFor(MovieResponse.class).slash(movieResponse.getId()).slash("movieReviews").withRel("movieReviews");
return new Resource<>(movieResponse, self, rel, movieGenres, movieReviewsa);
}
}
When i try to use it in my controller like the following :
#RestController
public class MovieController {
private MovieService movieService;
private PagedResourcesAssembler<MovieResponse> pagedAssembler;
private MovieResourceAssembler movieResourceAssembler;
#Autowired
public MovieController(MovieService movieService, PagedResourcesAssembler<MovieResponse> pagedAssembler, MovieResourceAssembler movieResourceAssembler) {
this.movieService = movieService;
this.pagedAssembler = pagedAssembler;
this.movieResourceAssembler = movieResourceAssembler;
}
#GetMapping("/movies")
public ResponseEntity<?> getAllMovies(#RequestParam(value = "page", defaultValue = DEFAULT_PAGE_NUMBER) String page,
#RequestParam(value = "size", defaultValue = DEFAULT_PAGE_SIZE) String size,
#RequestParam(value = "sort", defaultValue = "createdAt") String sort,
#RequestParam(value = "direction", defaultValue = "desc") String direction) {
PagedResponse<MovieResponse> response = this.movieService.getAllMovies(page, size, sort, direction);
return ResponseEntity.ok(this.pagedAssembler.toResource(response, this.movieResourceAssembler));
}
}
got the following Error:
Cannot resolve method 'toResource(com.movies.mmdb.util.PagedResponse, com.movies.mmdb.controller.MovieResourceAssembler)'
in this line: return ResponseEntity.ok(this.pagedAssembler.toResource(response, this.movieResourceAssembler));
i think toResource accept org.springframework.data.domain.Page; while i'm passing response variable which is PagedResponse.
How can i fix that ?

If toResource method receives a Page<T> as the first parameter, then why don't you use that data type? If you need to extend Page then your class should implement such interface to fulfill the method signature:
public class PagedResponse<T> implements Page<MovieResponse>

Related

Room TypeConverter not working

I have an issue with Room not recognizing my converter. Error:
Cannot figure out how to save this field into database. You can consider adding a type converter for it.
I need to store some maps and sets in my database. What am I doing wrong? Does room not like interfaces or generics?
code: (sorry for all the fields and class name, they are a mixture of English and Czech to not have same names as some java classes):
Converter (only part)
public class MyConverter {
/**
* makes a string like 1;2;3;5;4;8;1;6;8;4 from a collection of integers.
*/
#TypeConverter
public static #NonNull String toString(#NonNull Collection<Integer> c) {
StringBuilder sb = new StringBuilder();
for (Integer item : c) {
sb.append(item.toString() + ";");
}
sb.delete(sb.length()-1,sb.length()-1);
return sb.toString();
}
/**
* makes a Set<Integer> from string like 1;2;3;4;5;6
* #throws NumberFormatException on incorrect input
*/
#TypeConverter
public static#NonNull Set<Integer> toIntegerSet(#NonNull String s) {
Set<Integer> set = new LinkedHashSet<>();
String[] split = s.split(";");
try {
for (String item : split) {
set.add(Integer.parseInt(item));
}
}catch (NumberFormatException e){
throw new NumberFormatException("Could not make set of integers (like 1;2;3;8;7) from \"" + s +"\"");
}
return set;
}
}
Database:
#Database(entities = {SQLUkol.class,SQLPredmet.class,SQLList.class},version = 1)
#TypeConverters({MyConverter.class})
public abstract class AppDatabase extends RoomDatabase {
public abstract MojeDAO mojeDao();
}
One of the entities (getters, setters and constructors not included):
#Entity(primaryKeys = {"id", "list_id"},
indices = {#Index("list_id")},
foreignKeys = #ForeignKey(entity = SQLList.class, parentColumns = "id",
childColumns = "list_id", onDelete = ForeignKey.CASCADE),
tableName = "ukols")
public class SQLUkol implements Comparable<SQLUkol> {
#ColumnInfo(name = "list_id")
private final int listID;
private final int id;
private String title;
#ColumnInfo(name = "title_changed")
private boolean titleChanged = false;
private String notes;
#ColumnInfo(name = "notes_changed")
private boolean notesChanged = false;
private boolean completed;
#ColumnInfo(name = "completed_changed")
private boolean completedChanged = false;
private LocalDate date;
#ColumnInfo(name = "date_changed")
private boolean dateChanged = false;
#Embedded
private final SQLData data;
}
Room does not like generics much. I had to do this:
#TypeConverter
public static String toString1(Map<String, String> m){
...
}
#TypeConverter
public static String toString2(Map<Integer, String> m){
...
}
#TypeConverter
public static String toString3(Set<Integer> s){
...
}
#TypeConverter
public static String toString4(List<Integer> l){
...
}
not just
#TypeConverter
public static String toString(Map m){
...
}
#TypeConverter
public static String toString(Collection<Integer> c){
...
}

Hibernate one to many update with Spring HibernateTemplate

public class AfpProcessSummaryDetail implements Serializable {
private String srNo;
private String fileName;
private String status;
private String location;
private String comments;
private Character convertStatus;
private AfpProcessDetail afpProcessDetail;
public AfpProcessSummaryDetail() {
}
public AfpProcessSummaryDetail(String srNo, String fileName, String status, String location, String comments,
AfpProcessDetail afpProcessDetail) {
this.srNo = srNo;
this.fileName = fileName;
this.status = status;
this.location = location;
this.comments = comments;
this.afpProcessDetail = afpProcessDetail;
}
#ManyToOne
#JoinColumn(name = "PROCESSDETAIL")
public AfpProcessDetail getAfpProcessDetail() {
return afpProcessDetail;
}
AfpProcessDetail
public class AfpProcessDetail implements Serializable {
private String processID;
private String processDate;
private Integer fileCount;
private Integer successCount;
private Integer failureCount;
private Character active;
private Set<AfpProcessSummaryDetail> processSummaryDetails = new HashSet<AfpProcessSummaryDetail>(0);
public AfpProcessDetail() {
}
public AfpProcessDetail(String processID, String processDate, Integer fileCount, Integer successCount,
Integer failureCount) {
this.processID = processID;
this.processDate = processDate;
this.fileCount = fileCount;
this.successCount = successCount;
this.failureCount = failureCount;
}
public AfpProcessDetail(String processID, String processDate, Integer fileCount, Integer successCount,
Integer failureCount, Set<AfpProcessSummaryDetail> processSummaryDetails) {
this.processID = processID;
this.processDate = processDate;
this.fileCount = fileCount;
this.successCount = successCount;
this.failureCount = failureCount;
this.processSummaryDetails = processSummaryDetails;
}
#Column(name = "FAILURECOUNT")
public Integer getFailureCount() {
return failureCount;
}
public void setFailureCount(Integer failureCount) {
this.failureCount = failureCount;
}
#Column(name = "FILECOUNT")
public Integer getFileCount() {
return fileCount;
}
public void setFileCount(Integer fileCount) {
this.fileCount = fileCount;
}
#Column(name = "PROCESSDATE")
public String getProcessDate() {
return processDate;
}
public void setProcessDate(String processDate) {
this.processDate = processDate;
}
#Id
#Column(name = "PROCESSID", unique = true, nullable = false)
public String getProcessID() {
return processID;
}
public void setProcessID(String processID) {
this.processID = processID;
}
#Column(name = "SUCESSCOUNT")
public Integer getSuccessCount() {
return successCount;
}
public void setSuccessCount(Integer successCount) {
this.successCount = successCount;
}
#JoinColumn(name="PROCESSDETAIL")
#OneToMany(cascade=CascadeType.ALL,fetch = FetchType.EAGER)
public Set<AfpProcessSummaryDetail> getProcessSummaryDetails() {
return processSummaryDetails;
}
public void setProcessSummaryDetails(Set<AfpProcessSummaryDetail> processSummaryDetails) {
this.processSummaryDetails = processSummaryDetails;
}
Code for updating
public String updateSummaryDetails(ViewFile viewFile, String codeID) {
if (viewFile != null && codeID != null) {
HibernateTemplate transactionTemplate = new HibernateTemplate(sessionFactory, true);
Object result = transactionTemplate.execute(new HibernateCallback<Object>() {
#Override
public Object doInHibernate(org.hibernate.Session session) throws HibernateException, SQLException {
AfpProcessSummaryDetail processSummary =null,newProcessSummary =null;
AfpProcessDetail processDetail = (AfpProcessDetail)session.get(AfpProcessDetail.class,codeID);
List<FileProperty> fileList = viewFile.getFileList();
Set<AfpProcessSummaryDetail> setProcessSummary=new HashSet<AfpProcessSummaryDetail>();
Set<AfpProcessSummaryDetail> modSetProcessSummary=new HashSet<AfpProcessSummaryDetail>();
setProcessSummary =processDetail.getProcessSummaryDetails();
Iterator<AfpProcessSummaryDetail> itrProcessSumm=setProcessSummary.iterator();
int srNo = 0;
while (itrProcessSumm.hasNext()){
processSummary =(AfpProcessSummaryDetail)itrProcessSumm.next();
for (FileProperty fileProperty : fileList) {
newProcessSummary =new AfpProcessSummaryDetail();
newProcessSummary.setSrNo(codeID + "" + srNo);
newProcessSummary.setFileName(fileProperty.getName());
newProcessSummary.setLocation(fileProperty.getPath());
newProcessSummary.setComments(fileProperty.getComment());
newProcessSummary.setStatus(fileProperty.getStatus());
newProcessSummary.setConvertStatus(fileProperty.getConvertStatus());
newProcessSummary.setAfpProcessDetail(processDetail);
modSetProcessSummary.add(newProcessSummary);
/*if (processSummary.getFileName().trim().equals(fileProperty.getName().trim())){
System.out.println("Element removed");
itrProcessSumm.remove();
modSetProcessSummary.add(newProcessSummary);
break;
}*/
srNo++;
}
}
// setProcessSummary.addAll(modSetProcessSummary);
processDetail.setProcessSummaryDetails(modSetProcessSummary);
processDetail.setFailureCount(viewFile.getExceptionNo());
processDetail.setSuccessCount(viewFile.getSuccessNo());
processDetail.setActive(viewFile.getActive());
transactionTemplate.flush();
session.merge(processDetail);
System.out.println("updated successfully");
return codeID;
}
});
Desired result
I want to perform a one to many update -AfpProcessSummaryDetail which is related to AfpProcessDetail via Set. When I try to replace the set values for update it tries to update the primary key to null. If I don't replace the updates don't take place. If I set cascade it gives error -a different object with the same identifier value was already associated with the session:
[com.aurionpro.convertor.dto.AfpProcessSummaryDetail#15a236f‌​fc961];
nested exception is org.hibernate.NonUniqueObjectException: a
different object with the same identifier value was already associated
with the session.
Please suggest

Spring + hibernate : Expected type: java.util.SortedSet, actual value: org.hibernate.collection.internal.PersistentSet

I have a Client.class which has a OneToMany relation with Posto.class.
#Entity
#Table(name = "client", catalog = "SMARTPARK")
public class Client implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private int idClient;
private String nomeClient;
private int numPosti;
private int numLuci;
private String currentIp;
private boolean online;
private String prop;
private SortedSet<Posto> posti = new TreeSet<>();
private SortedSet<Luce> luci = new TreeSet<>();
public Client() {
}
public Client(int idClient, String nomeClient, int numPosti, int numLuci,
String currentIp, boolean online, String prop,
SortedSet<Posto> posti, SortedSet<Luce> luci) {
this.idClient = idClient;
this.nomeClient = nomeClient;
this.numPosti = numPosti;
this.numLuci = numLuci;
this.currentIp = currentIp;
this.prop = prop;
this.online = online;
this.posti = posti;
this.luci = luci;
}
#Id
#Column(name = "id_client", unique = true, nullable = false)
public int getIdClient() {
return this.idClient;
}
public void setIdClient(int idClient) {
this.idClient = idClient;
}
#Column(name = "nome_client", nullable = false, length = 65535)
public String getNomeClient() {
return this.nomeClient;
}
public void setNomeClient(String nomeClient) {
this.nomeClient = nomeClient;
}
#Transient
public int getNumPosti() {
return this.numPosti;
}
public void setNumPosti(int numPosti) {
this.numPosti = numPosti;
}
#Transient
public int getNumLuci() {
return this.numLuci;
}
public void setNumLuci(int numLuci) {
this.numLuci = numLuci;
}
#Column(name = "client_ip", nullable=true)
public String getCurrentIp() {
return currentIp;
}
public void setCurrentIp(String currentIp) {
this.currentIp = currentIp;
}
#Column(name="online")
public boolean isOnline() {
return online;
}
public void setOnline(boolean online) {
this.online = online;
}
#Column(name="prop")
public String getProp() {
return prop;
}
public void setProp(String prop) {
this.prop = prop;
}
#OneToMany(fetch = FetchType.EAGER, cascade=CascadeType.ALL, mappedBy = "client", orphanRemoval=true)
#OrderBy("numeroPosto ASC")
public Set<Posto> getPosti() {
return posti;
}
public void setPosti(SortedSet<Posto> posti) {
this.posti = posti;
}
#OneToMany(fetch = FetchType.EAGER, cascade=CascadeType.ALL, mappedBy = "client", orphanRemoval=true)
#OrderBy("numeroLuce ASC")
public SortedSet<Luce> getLuci() {
return luci;
}
public void setLuci(SortedSet<Luce> luci) {
this.luci = luci;
}
This was made because a use the Set in a responseEntity Controller and I need to preserve the order of how postiis displayed in the Json output.
So in Posto.class I implemented Comparable interface, overrding the compareTo method
#Override
public int compareTo(Posto o) {
if(this.numeroPosto == o.numeroPosto){
return 0;
} else {
return this.numeroPosto > o.numeroPosto ? 1 : -1;
}
Now, when calling my controller, i got this error from Hibernate:
2016-03-30 16:18:07.486 ERROR [http-nio-8080-exec-6]: HHH000123: IllegalArgumentException in class: it.besmart.models.Client, setter method of property: posti
2016-03-30 16:18:07.486 ERROR [http-nio-8080-exec-6]: HHH000091: Expected type: java.util.SortedSet, actual value: org.hibernate.collection.internal.PersistentSet
How can i solve it? Hibernate changes my SortedSet in a PersistentSet, do I have to use this one to set my posti with the order i want?
The problem is you defined your posti and Luci as concrete SortSet. Hibernate PersistentSet implements generic Set interface. All you need to do is changing SortSet to generic Set and modify getters, setters accordingly.
private Set<Posto> posti;
private Set<Luce> luci;

Can Orika map nested collections?

I would like to map a field with nested collection using Orika library. My field in class is defined as:
private final List<List<Pojo>> list = new LinkedList<List<Pojo>>();
Pojo is a simple POJO class. Unfortunately I've got a MappingException caused by NullPointerException in Orika's internal logic.
Did I do something in wrong way? Maybe I need to use Custom Mapping feature?
EDIT:
Here is my code:
public class Pojo {
private int field;
public int getField() {
return field;
}
public void setField(final int field) {
this.field = field;
}
}
public class Source {
private final List> list = new LinkedList>();
public List<List<Pojo>> getList() {
return list;
}
}
public class Destination {
private final List> listDest = new LinkedList>();
public List<List<Pojo>> getListDest() {
return listDest;
}
}
public class Main {
public static void main(final String[] args) {
final MapperFactory factory = new DefaultMapperFactory.Builder().build();
factory.classMap(Source.class, Destination.class).field("list", "listDest").byDefault().register();
final Source src = new Source();
final LinkedList<Pojo> nestedList = new LinkedList<Pojo>();
final Pojo pojo = new Pojo();
pojo.setField(8978);
nestedList.add(pojo);
src.getList().add(nestedList);
final MapperFacade facade = factory.getMapperFacade();
final Destination dest = facade.map(src, Destination.class);
System.out.println(dest.getListDest().get(0).get(0).getField());
}
}
Execution above code results this Exception:
Exception in thread "main" ma.glasnost.orika.MappingException: Error encountered while mapping for the following inputs:
rawSource=com.bbh.nested.Source#39185ce6
sourceClass=class com.bbh.nested.Source
destinationClass=class com.bbh.nested.Destination
You can see this Example:
public class ShopEntity {
private Long id;
private String name;
private String logo;
private String url;
private ProductCategory mainCategory;
private Set<ShopRel> shopRels = new HashSet<>(0);
private Account account;
// Assume getter/setter
}
public class ProductCategory extends BaseEntity {
private Long id;
private String name;
// Assume getter/setter
}
public class ShopRel {
private Long id;
private SaleChannel saleChannel;
private Boolean enabled;
// Assume getter/setter
}
public class SaleChannel {
private Long id;
private String name;
private String image;
private String description;
private Boolean active;
// Assume getter/setter
}
public class ShopDto {
private Long id;
private String name;
private String logo;
private String url;
private Long mainCategory;
private Set<ShopRelDto> shopRelDtos = new HashSet<ShopRelDto>();
// Assume getter/setter
}
public class ShopRelDto {
private Long channelId;
private String name;
private Boolean enabled;
// Assume getter/setter
}
public class MapperUtils {
private static final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
private static final MapperFacade mapper = mapperFactory.getMapperFacade();
static {
mapperFactory.classMap(ShopEntity.class, ShopDto.class)
.field("mainCategory.id", "mainCategory")
.fieldMap("shopRels", "shopRelDtos").aElementType(ShopRel.class).bElementType(ShopRelDto.class).add()
.register();
mapperFactory.classMap(ShopRel.class, ShopRelDto.class)
.field("saleChannel.id", "channelId")
.field("saleChannel.name", "name")
.field("enabled", "enabled")
.register();
}
public static final void map(Object source, Object distance) {
mapper.map(source, distance);
}
public static final <T> T map(Object source, Class<T> destinationClass){
return mapper.map(source, destinationClass);
}
public static void main(String[] args) {
ShopEntity shop = new ShopEntity();
shop.setId(1L);
shop.setName("ABC");
ProductCategory productCategory =new ProductCategory();
productCategory.setId(10L);
shop.setMainCategory(productCategory);
Set<ShopRel> shopRels = new HashSet<>(0);
ShopSaleChannelRel channelRel = new ShopSaleChannelRel();
channelRel.setId(1L);
channelRel.setEnabled(true);
SaleChannel saleChannel = new SaleChannel();
saleChannel.setId(1L);
saleChannel.setName("Channel1");
channelRel.setSaleChannel(saleChannel);
shopRels.add(channelRel);
shop.setShopRels(shopRels);
ShopDto shopDto = map(shop, ShopDto.class);
System.out.println(shopDto);
}
}
It may need a custom mapping via customize if there is lot of cases like this you can extend Orika via Specifications to support this use case

#ResponseBody Serialization Error using ArrayLists

I am getting the following error trying to return my object as a JSON string using SpringMVC #ResponseBody:
org.codehaus.jackson.map.JsonMappingException: No serializer found for class com.ResourceResultSetCol and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: com.medplus.devops.pdt.server.ResourceResultSet["cols"]->java.util.ArrayList[0])
GraphResultSet.java:
#Controller
#RequestMapping("/pdt")
public class GraphResultSet {
#RequestMapping(value = "/getResourceResultSet", method = RequestMethod.GET)
public #ResponseBody
ResourceResultSet getResourceResultSet(
#RequestParam(value = "resourceId", required = true) int resourceId) {
return new ResourceResultSet(resourceId);
}
}
ResourceResultSet.java:
public class ResourceResultSet implements Serializable {
public String resourceName;
public ArrayList<ResourceResultSetCol> cols;
public ArrayList<ResourceResultSetRow> rows;
ResourceResultSet(int id) {
resourceName = "Graph " + id;
cols = new ArrayList<ResourceResultSetCol>();
cols.add(new ResourceResultSetCol("col1","Timestamp","date"));
cols.add(new ResourceResultSetCol("col2","Value","number"));
int randomNumberOfResults = new Random().nextInt(5);
int numberOfResults[] = new int[] {12,288,2016,8928,107136}; // hour, day, week, month, year
Calendar now = Calendar.getInstance();
rows = new ArrayList<ResourceResultSetRow>();
for (int resultIndex = 0; resultIndex <= numberOfResults[randomNumberOfResults]; ++resultIndex) {
now.setTime(now.getTime());
now.add(Calendar.MINUTE, resultIndex * -5);
this.rows.add(new ResourceResultSetRow(now.getTime().toString(), new Random().nextInt(101)));
}
}
}
ResourceResultSetCol.java:
public class ResourceResultSetCol implements Serializable {
private String id;
private String label;
private String type;
public ResourceResultSetCol(String id, String label, String type){
this.id = id;
this.label = label;
this.type = type;
}
}
ResourceResultSetRow.java:
public class ResourceResultSetRow implements Serializable {
private String timestamp;
private int result;
ResourceResultSetRow(String timestamp, int result) {
this.timestamp = timestamp;
this.result = result;
}
}
The key message is and no properties discovered to create BeanSerializer: your classes ResourceResultSetCol and ResourceResultSetRow should have default public constructor and getters/setters for all properties.

Categories