Migrate Spring hateos ResourceAssembler to RepresentationModelAssembler - java

According to this post ResourceAssembler is changed to RepresentationModelAssembler
I have this code which is using Spring HATEOAS 1.0:
import org.springframework.hateoas.ResourceAssembler;
public class BaseAssembler<T extends BaseTransaction, D extends BaseResource>
implements ResourceAssembler<T, D> {
...
}
After migration to implementation 'org.springframework.boot:spring-boot-starter-hateoas:2.6.4'
I changed it to:
public class BaseAssembler<T extends BaseTransaction, D extends BaseResource>
implements RepresentationModelAssembler<T, D> {
.........
}
But I get error:
Type parameter 'D' is not within its bound; should extend 'org.springframework.hateoas.RepresentationModel<?>'
Do you know how I can fix this issue?

The compiler is reporting that the type parameter D is not within its bound in your definition:
public class BaseAssembler<T extends BaseTransaction, D extends BaseResource>
implements RepresentationModelAssembler<T, D> {
.........
}
In other words, it means that you cannot use D extends BaseResource to implement RepresentationModelAssembler<T, D> (note the type parameter D here) because that type should extend 'org.springframework.hateoas.RepresentationModel<?>'.
RepresentationModelAssembler gives you the ability to convert between domain types, your entities, to RepresentationModels, a based class conceived to enrich your DTOs to collect links.
It is defined as follows:
public interface RepresentationModelAssembler<T, D extends RepresentationModel<?>>
Note again the definition of the type parameter D.
In your code you need to use something like:
public class BaseAssembler<T extends BaseTransaction, D extends RepresentationModel<?>>
implements RepresentationModelAssembler<T, D> {
.........
}
Please, consider read for instance some this or this other article, they provide a great variety of examples and uses cases about showcasing how you can implement the desired behavior.
For example, given the following entity, extracted from one of the cited articles:
#Entity
public class Director {
#Id
#GeneratedValue
#Getter
private Long id;
#Getter
private String firstname;
#Getter
private String lastname;
#Getter
private int year;
#OneToMany(mappedBy = "director")
private Set<Movie> movies;
}
And the following DTO:
#Builder
#Getter
#EqualsAndHashCode(callSuper = false)
#Relation(itemRelation = "director", collectionRelation = "directors")
public class DirectorRepresentation extends RepresentationModel<DirectorRepresentation> {
private final String id;
private final String firstname;
private final String lastname;
private final int year;
}
Your RepresentationModelAssembler will look like:
#Component
public class DirectorRepresentationAssembler implements RepresentationModelAssembler<Director, DirectorRepresentation> {
#Override
public DirectorRepresentation toModel(Director entity) {
DirectorRepresentation directorRepresentation = DirectorRepresentation.builder()
.id(entity.getId())
.firstname(entity.getFirstname())
.lastname(entity.getLastname())
.year(entity.getYear())
.build();
directorRepresentation.add(linkTo(methodOn(DirectorController.class).getDirectorById(directorRepresentation.getId())).withSelfRel());
directorRepresentation.add(linkTo(methodOn(DirectorController.class).getDirectorMovies(directorRepresentation.getId())).withRel("directorMovies"));
return directorRepresentation;
}
#Override
public CollectionModel<DirectorRepresentation> toCollectionModel(Iterable<? extends Director> entities) {
CollectionModel<DirectorRepresentation> directorRepresentations = RepresentationModelAssembler.super.toCollectionModel(entities);
directorRepresentations.add(linkTo(methodOn(DirectorController.class).getAllDirectors()).withSelfRel());
return directorRepresentations;
}
}
In terms of your interfaces and object model:
#Entity
public class Director extends BaseTransaction{
#Id
#GeneratedValue
#Getter
private Long id;
#Getter
private String firstname;
#Getter
private String lastname;
#Getter
private int year;
#OneToMany(mappedBy = "director")
private Set<Movie> movies;
}
public class DirectorRepresentationAssembler
extends BaseAssembler<Director, DirectorRepresentation>
implements RepresentationModelAssembler<Director, DirectorRepresentation> {
//... the above code
}
DirectorRepresentation is the same as presented above.
The Spring HATEOAS reference guide itself provides some guidance as well about the changes performed in Spring HATEOAS 1.0 and about how to migrate from the previous version. It even includes a script that may be of help.
In any case, as indicated above, in your use case you only need to modify the BaseAssembler interface to be defined in terms of the type D extends RepresentationModel<?>; then try relating in some way BaseResource to RepresentationModel or get rid of BaseResources and use RepresentationModels instead.
For example, you couild try defining BaseResource as follows:
public class BaseResource extends RepresentationModel<BaseResource>{
// your implementation
}
Then, the bound will be right:
public class BaseAssembler<T extends BaseTransaction, D extends BaseResource>
implements RepresentationModelAssembler<T, D> {
// your implementation
}
With these changes, DirectorRepresentation will extend BaseResource:
public class DirectorRepresentation extends BaseResource {
}
And you can extend BaseAssembler like this:
public class DirectorRepresentationAssembler
extends BaseAssembler<Director, DirectorRepresentation>
implements RepresentationModelAssembler<Director, DirectorRepresentation> {
// your implementation
}
In my opinion, the code you published in your repository is mostly fine. I think the only problem is in this line of code, as I mentioned before, I think you need to provide the type parameter when defining your BaseResource class. For instance:
package com.hateos.test.entity.web.rest.resource;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.joda.time.DateTime;
import org.springframework.hateoas.RepresentationModel;
import java.util.UUID;
public class BaseResource extends RepresentationModel<BaseResource> {
#JsonProperty
#ApiModelProperty(position = 1, required = true)
public UUID id;
#JsonProperty
public DateTime creationTime;
#JsonProperty
public DateTime lastUpdatedTime;
}
Please, note the inclusion of the code fragment RepresentationModel<BaseResource> after the extends keyword.
I am not sure if it will work but at least with this change every compiles fine and it seems to work properly.

Related

Jackson Deserialize Polymorphic Wrapper With Polymorphic Member

I have a parent wrapper class that I need to use to transport data between our systems in a unified format, and I need to be able to deduce the sub-type of it. I was able to make this work with a custom deserializer, but I've been tasked with re-implementing it based on annotations. I've gotten partway there, but I'm hitting an InvalidDefinitionException now that I think stems from Jackson looking for a 1-String constructor to my inner type, instead of using the #JsonCreator all-args constructor that used to work before with my Deserializer.
There should be a way to have the TypeInfo and SubTypes cascade down the hierarchy, even with a wrapper/holder type, right?
Wrapper Classes:
#JsonTypeInfo(use=JsonTypeInfo.NAME, property="type", visible=true)
#JsonSubTypes({
#JsonSubTypes.Type(value=UserApiMessage.class, name=UserPayloadNameEnum.Constant.LOGIN),
#JsonSubTypes.Type(value=UserApiMessage.class, name=UserPayloadNameEnum.Constant.CONFIG_REQUEST),
#JsonSubTypes.Type(value=SystemApiMessage.class, name=SystemPayloadNameEnum.Constant.METRICS_REQUEST),
#JsonSubTypes.Type(value=SystemApiMessage.class, name=SystemPayloadNameEnum.Constant.METRICS_RESPONSE),
...
})
public abstract class ApiMessage {
public abstract String getRequestId();
public abstract Enum getType(); //Object mapper de/serializes on Enum.name
public abstract ApiPayload getPayload();
}
public class UserApiMessage extends ApiMessage {
private final String requestId;
private final UserPayloadNameEnum type;
#JsonTypeInfo(use=JsonTypeInfo.NAME, include=JsonTypeInfo.As.EXTERNAL_PROPERTY, property="type", visible=true)
#JsonSubTypes({
#JsonSubTypes.Type(value=Login.class, name=UserPayloadNameEnum .Constant.LOGIN),
#JsonSubTypes.Type(value=ConfigRequest.class, name=UserPayloadNameEnum.Constant.CONFIG_REQUEST)
})
private final UserPayload payload;
//All-Args #JsonCreator constructor
//All #Override getters
}
public class SystemApiMessage extends ApiMessage {
private final String requestId;
private final SystemPayloadNameEnum type;
#JsonTypeInfo(use=JsonTypeInfo.NAME, include=JsonTypeInfo.As.EXTERNAL_PROPERTY, property="type", visible=true)
#JsonSubTypes({
#JsonSubTypes.Type(value=MetricsRequest.class, name=SystemPayloadNameEnum .Constant.METRICS_REQUEST),
#JsonSubTypes.Type(value=MetricsResponse.class, name=SystemPayloadNameEnum .Constant.METRICS_RESPONSE)
})
private final SystemPayload payload;
//All-Args #JsonCreator constructor
//All #Override getters
}
A parent ApiPayload class, which is polymorphic
public abstract class ApiPayload {
...
}
public abstract class UserPayload extends ApiPayload {
...
}
public abstract class SystemPayload extends ApiPayload {
...
}
And their concrete implementations.
public class Login extends UserPayload {
//All-Args #JsonCreator as constructor
}
public class ConfigRequest extends UserPayload {
//All-Args #JsonCreator as constructor
}
public class MetricsRequest extends SystemPayload {
//All-Args #JsonCreator as constructor
}
public class MetricsResponse extends SystemPayload {
//All-Args #JsonCreator as constructor
}
When executing mapper.readValue(json, ApiMessage.class); on a well-formed "Login" request that worked with my old custom deserializer, I get the following error.
InvalidDefinitionException: Cannot construct instance of 'com.foo.bar...UserApiMessage', problem: argument type mismatch.
at(Source: (String)" {
My well-formed json
}"; line 15, column: 1]
So if I'm reading this correctly, Jackson is looking for a 1-string constructor of UserApiMessage, rather than continuing the type deduction. What am I doing wrong?
Side Note: if I add the #JsonTypeInfo with WRAPPER_OBJECT and repeat the SubTypes on the class signature for UserPayload, I actually get to the point it tries to construct a Login object, but then all of the args are missing (given default/null values).

CustomRepository clashes with <S>save(S) in org.springframework.data.repository.CrudRepository return type java.lang.Long is not compatible with S

I am trying to save a record in DB, which in return should return me the primary key.
here is my Entity Class:
#Entity
public class CustomEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
private Integer eventTypeId;
//getter setter here
}
Here goes my repository:
public interface CustomRepository extends JpaRepository<CustomEntity, Long> {
Long save(customRepository);
}
When i try to call the repository interface in my Service class, I get this Exception at compile time:
Error:(32, 8) java: save(model.CustomEntity) in repository.CustomRepository clashes with <S>save(S)
in org.springframework.data.repository.CrudRepository return type java.lang.Long is not compatible with S
I understand JPA Repository extends PaginationAndSorting which in returns extends CrudRepository, how do i resolve this issue.
the methods signature is
<S extends T> S save(S entity)
so you need to return the same Type as you pass as parameter. In your case CustomEntity.
The parameter's type and return value should be the same.
try to change your code:
public interface CustomRepository extends JpaRepository<CustomEntity, Long> {
CustomEntity save(customRepository);
}
and then get id value from CustomEntity object for example:
public void someMethod(){
CustomEntity entity = repo.save(new CustomEntity());
Long savedId = entity.getId();
}

How to search in h2 database Play Framework 2.7.3?

I need to search in an h2 database for Company class instances that have a specific code, but I can't figure out how.
I have tried using the Finder class, but there don't seem to be any find methods in the version i am using except findbyid().
Here is the beginning of my Company class:
#Entity
public class Company extends Model {
#Id
public Integer id;
public String code;
public String name;
public String adress;
public String fiscalCode;
public String bankAccount;
public static Finder<Integer, Company> find = new Finder<>(Company.class);
Thank you!
Depending on the version of Play you are using I'd suggest to try the following:
Company.find.where().like("code", "%foo%").findList();
or
Define your finder
public class CompanyFinder extends Finder<Long,Company> {
public CompanyFinder() {
super(Company.class);
}
// Add finder methods ...
public Company byCode(String code) {
return query().eq("code", code).findOne();
}
}
Update your entity to reference this finder:
#Entity
public class Company extends BaseModel {
public static final CompanyFinder find = new CompanyFinder();
...
}
Call the finder:
Company c = Company.find.byCode("foo");

org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type Account to type AllAccount

I am using SpringBoot and JPA to call db,
I am getting exception as
org.springframework.core.convert.ConverterNotFoundException: No
converter found capable of converting from type
[com.xxx.central.model.Account] to type
[com.xxx.central.model.AllAccount]
Below is my code
Account.java
#Entity
#Table(name = "account")
public class Account implements Serializable {
AllAccount.java
#Entity(name="allAccounts")
#Table(name = "account")
public class AllAccount implements Serializable {
AccountRepository.java
#RepositoryDefinition(domainClass = Account.class, idClass = Integer.class)
public interface AccountRepository extends CrudRepository<Account, Integer>
{
public List<AllAccount>
findByIsActiveAndClientNameIgnoreCaseContainingOrderByAccountCreatedDesc(
Boolean active, String clientNameSearchString);
}
When i am calling above repository from my service class i getting exception.
Where i am going wrong?
Thank you.
public interface AccountRepository extends CrudRepository<Account, Integer>
This line makes your Repository class only return types of object Account.
That's why when you call
public List<AllAccount>
findByIsActiveAndClientNameIgnoreCaseContainingOrderByAccountCreatedDesc
it tries to covert from type Account to AllAccount which it can't and hence the exception.
Either you create a different repository class for AllAccount or change this one to return AllAccount by changing to
public interface AccountRepository extends CrudRepository<AllAccount, Integer>

Spring Data Mongodb Convert Document Falsely

I have a document structure which has some generic class. For writing to mongodb everything is fine. But when reading documents from mongodb spring data converts document into object falsely. It converts a subdocument with another type. Both types (actual subcollection type and falsely converted type) are inherit from same abstract class.
Model Classes:(getter setters are generated by lombok )
#Data
public abstract class CandidateInfo {
private String _id;
}
#Data
public class CandidateInfoContainer<E extends CandidateInfo> {
private String _id;
private int commentCount = 0;
#Valid
private List<E> values = new ArrayList<>();
}
#Data
public class Responsibility extends CandidateInfo {
#NotNull
private String responsibilityId;
#ReadOnlyProperty
private String responsibilityText;
}
#Data
public class Experience extends CandidateInfo {
#Valid
private CandidateInfoContainer<Responsibility> responsibilities;
}
#Document
#JsonInclude(JsonInclude.Include.NON_NULL)
#Data
public class Candidate {
private String _id;
#Valid
private CandidateInfoContainer<Experience> experiences;
}
And if you create a mongoRepository like below:
#Repository
public interface CandidateRepository extends MongoRepository<Candidate,String>{
}
And use it like:
#Autowired
private CandidateRepository candidateRepository;
Candidate candidate = candidateRepository.findOne("documentId");
Then spring data mongo mapping converter creates candidates.experiences.responsibilities.values list as Experince list but it should be Responsibility list.
You can find a demo project in this link and more information about the issue. Can anyone point out what is wrong? Otherwise i have to write my own converter(demo has one)
If there is any unclear thing, you can ask.
Thanks.
I open an issue in spring-data-mongo here. Appareantly I caught a bug! Thanks everyone

Categories

Resources