Inherited methods in Spring-Data-Neo4j repository interfaces not working - spring

I have an abstract domain class containing a uid field, looking as below:
public abstract class GraphEntityWithUid extends GraphEntity {
private String uid = CommonUtils.newUid();
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
}
And, an abstract repository for it:
public abstract interface GraphEntityWithUidRepository<T extends GraphEntityWithUid> extends GraphRepository<T> {
public T findByUid(String uid);
}
I have a concrete domain class that inherits the uid, looking as below:
#NodeEntity
public class Attachment extends GraphEntityWithUid {
...
}
And, its repository looks as below:
public interface AttachmentRepository extends GraphEntityWithUidRepository<Attachment> {
}
Now, when I use the findByUid method as below:
// returns null
attachmentRepository.findByUid(uid);
it always returns null. However, if I re-declare the method in the AttachmentRepository as below, it works properly:
public interface AttachmentRepository extends GraphEntityWithUidRepository<Attachment> {
// Shouldn't this be automatically inherited??
public Attachment findByUid(String uid);
}
Why should I need to re-declare findByUid method in AttachmentRepository? Shouldn't it be automatically inherited from GraphEntityWithUidRepository?

Related

Can't mapping the abstract class using model mapper

I have an issue while mapping the DTO to Entity the CVV and expiration date not working but the number of the card is working fine, and mapping the did I miss any configuration ??
I have class A inherit from PaymentRequestDTO and class B inherit PaymentRequest
and mapping using this.modelmapper().map(classA, classB);
Parent Class for A
public abstract class PaymentRequestDTO {
....
CardRequestDTO card;
...
}
CardDTO
public class CardRequestDTO {
#JsonProperty("name")
public String name;
#JsonProperty("number")
public String number;
#JsonProperty("expiryMonth")
public String expiryMonth;
#JsonProperty("expiryYear")
public String expiryYear;
#JsonProperty("cvv")
public String cvv;
}
Parent Class for B
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
public abstract class PaymentRequest{
#JsonProperty("card_number")
public String cardNumber;
#JsonProperty("expiry_date")
public String expiryDate;
#JsonProperty("card_security_code")
public String cardSecurityCode;
}
PropertyMap class
public class PaymentRequestConverter extends PropertyMap<PaymentRequestDTO, PaymentRequest> {
#Override
protected void configure() {
map().setCardNumber(source.getCard().getNumber());
map().setCardSecurityCode(source.getCard().getCvv());
map().setExpiryDate(FortHelper.expiryDate(source.getCard().getExpiryYear(), source.getCard().getExpiryMonth()));
}
}
Here is my model mapper
#Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.createTypeMap(PaymentRequestDTO.class, PaymentRequest.class).addMappings(new PaymentRequestConverter());
return modelMapper;
}
Model mapper version 3.1.0

Adding content property to serialization

Whenever I use a custom serializer in spring data rest, it adds a "content" property that wrapps the object returned, like:
{
"content":{
object properties...
},
_links: {
}
}
EDIT: Add configuration class
#Configuration
public class JacksonCustomizations {
#Bean
public Module rateModule() {
return new RateModule();
}
#SuppressWarnings("serial")
static class RateModule extends SimpleModule {
public RateModule() {
setMixInAnnotation(Package.class, RateModule.PackageMixin.class);
setMixInAnnotation(Section.class, RateModule.SectionMixin.class);
setMixInAnnotation(MainPart.class, RateModule.MainPartMixin.class);
setMixInAnnotation(SubPart.class, RateModule.SubPartMixin.class);
addSerializer(MaintenanceTask.class, new MaintenanceTaskSerializer());
addDeserializer(Package.class, new PackageDeserializer());
addDeserializer(Section.class, new SectionDeserializer());
addDeserializer(MainPart.class, new MainPartDeserializer());
addDeserializer(MaintenanceTask.class, new MaintenanceTaskDeserializer());
}
#JsonAutoDetect(fieldVisibility=Visibility.NONE, getterVisibility=Visibility.NONE, isGetterVisibility=Visibility.NONE)
static abstract class PackageMixin {
#JsonProperty("name") public abstract String getName();
#JsonProperty("sections") public abstract List<Section> getSections();
}
#JsonAutoDetect(fieldVisibility=Visibility.NONE, getterVisibility=Visibility.NONE, isGetterVisibility=Visibility.NONE)
static abstract class SectionMixin {
#JsonProperty("id") public abstract Long getId();
#JsonProperty("name") public abstract String getName();
}
#JsonAutoDetect(fieldVisibility=Visibility.NONE, getterVisibility=Visibility.NONE, isGetterVisibility=Visibility.NONE)
static abstract class MainPartMixin {
#JsonProperty("name") public abstract String getName();
#JsonProperty("subparts") public abstract List<SubPart> getSubParts();
}
#JsonAutoDetect(fieldVisibility=Visibility.NONE, getterVisibility=Visibility.NONE, isGetterVisibility=Visibility.NONE)
static abstract class SubPartMixin {
#JsonProperty("id") public abstract Long getId();
#JsonProperty("name") public abstract String getName();
}
static class MaintenanceTaskSerializer extends JsonSerializer<MaintenanceTask> {
#Override
public void serialize(final MaintenanceTask value, final JsonGenerator gen, final SerializerProvider serializers)
throws IOException, JsonProcessingException {
gen.writeStartObject();
gen.writeNumberField("id", value.getId());
gen.writeStringField("maintenanceRequirementId", value.getMaintenanceRequirementId());
gen.writeStringField("type", value.getType().toString());
gen.writeStringField("title", value.getTitle());
gen.writeStringField("description", value.getDescription());
gen.writeStringField("note", value.getNote());
gen.writeStringField("effectivity", value.getEffectivity());
gen.writeNumberField("procedureReference", value.getReferenceTask().getId());
gen.writeNumberField("aircraftModel", value.getAircraftModel().getId());
gen.writeNumberField("packageId", value.getPack().getId());
gen.writeNumberField("sectionId", value.getSection().getId());
gen.writeStringField("taskType", value.getTaskType().toString());
gen.writeEndObject();
}
}
}
}
But when I use spring data rest serialization without custom serializers, the property is not inserted.
How can I prevent this property from showing?
This is an known issue.
It has been reported here: https://jira.spring.io/browse/DATAREST-504
The issue points to this stack overflow question: Different JSON output when using custom json serializer in Spring Data Rest

Spring Data Rest Repository with abstract class / inheritance

I can't get Spring Data Rest with class inheritance working.
I'd like to have a single JSON Endpoint which handles all my concrete classes.
Repo:
public interface AbstractFooRepo extends KeyValueRepository<AbstractFoo, String> {}
Abstract class:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = MyFoo.class, name = "MY_FOO")
})
public abstract class AbstractFoo {
#Id public String id;
public String type;
}
Concrete class:
public class MyFoo extends AbstractFoo { }
Now when calling POST /abstractFoos with {"type":"MY_FOO"}, it tells me: java.lang.IllegalArgumentException: PersistentEntity must not be null!.
This seems to happen, because Spring doesn't know about MyFoo.
Is there some way to tell Spring Data REST about MyFoo without creating a Repository and a REST Endpoint for it?
(I'm using Spring Boot 1.5.1 and Spring Data REST 2.6.0)
EDIT:
Application.java:
#SpringBootApplication
#EnableMapRepositories
public class Application {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
I'm using Spring Boot 1.5.1 and Spring Data Release Ingalls.
KeyValueRepository doesn't work with inheritance. It uses the class name of every saved object to find the corresponding key-value-store. E.g. save(new Foo()) will place the saved object within the Foo collection. And abstractFoosRepo.findAll() will look within the AbstractFoo collection and won't find any Foo object.
Here's the working code using MongoRepository:
Application.java
Default Spring Boot Application Starter.
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
AbstractFoo.java
I've tested include = JsonTypeInfo.As.EXISTING_PROPERTY and include = JsonTypeInfo.As.PROPERTY. Both seem to work fine!
It's even possible to register the Jackson SubTypes with a custom JacksonModule.
IMPORTANT: #RestResource(path="abstractFoos") is highly recommended. Else the _links.self links will point to /foos and /bars instead of /abstractFoos.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Foo.class, name = "MY_FOO"),
#JsonSubTypes.Type(value = Bar.class, name = "MY_Bar")
})
#Document(collection="foo_collection")
#RestResource(path="abstractFoos")
public abstract class AbstractFoo {
#Id public String id;
public abstract String getType();
}
AbstractFooRepo.java
Nothing special here
public interface AbstractFooRepo extends MongoRepository<AbstractFoo, String> { }
Foo.java & Bar.java
#Persistent
public class Foo extends AbstractFoo {
#Override
public String getType() {
return "MY_FOO";
}
}
#Persistent
public class Bar extends AbstractFoo {
#Override
public String getType() {
return "MY_BAR";
}
}
FooRelProvider.java
Without this part, the output of the objects would be separated in two arrays under _embedded.foos and _embedded.bars.
The supports method ensures that for all classes which extend AbstractFoo, the objects will be placed within _embedded.abstractFoos.
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class FooRelProvider extends EvoInflectorRelProvider {
#Override
public String getCollectionResourceRelFor(final Class<?> type) {
return super.getCollectionResourceRelFor(AbstractFoo.class);
}
#Override
public String getItemResourceRelFor(final Class<?> type) {
return super.getItemResourceRelFor(AbstractFoo.class);
}
#Override
public boolean supports(final Class<?> delimiter) {
return AbstractFoo.class.isAssignableFrom(delimiter);
}
}
EDIT
Added #Persistent to Foo.java and Bar.java. (Adding it to AbstractFoo.java doesn't work). Without this annotation I got NullPointerExceptions when trying to use JSR 303 Validation Annotations within inherited classes.
Example code to reproduce the error:
public class A {
#Id public String id;
#Valid public B b;
// #JsonTypeInfo + #JsonSubTypes
public static abstract class B {
#NotNull public String s;
}
// #Persistent <- Needed!
public static class B1 extends B { }
}
Please see the discussion in this resolved jira task for details of what is currently supported in spring-data-rest regarding JsonTypeInfo. And this jira task on what is still missing.
To summarize - only #JsonTypeInfo with include=JsonTypeInfo.As.EXISTING_PROPERTY is working for serialization and deserialization currently.
Also, you need spring-data-rest 2.5.3 (Hopper SR3) or later to get this limited support.
Please see my sample application - https://github.com/mduesterhoeft/spring-data-rest-entity-inheritance/tree/fixed-hopper-sr3-snapshot
With include=JsonTypeInfo.As.EXISTING_PROPERTY the type information is extracted from a regular property. An example helps getting the point of this way of adding type information:
The abstract class:
#Entity #Inheritance(strategy= SINGLE_TABLE)
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME,
include=JsonTypeInfo.As.EXISTING_PROPERTY,
property="type")
#JsonSubTypes({
#Type(name="DECIMAL", value=DecimalValue.class),
#Type(name="STRING", value=StringValue.class)})
public abstract class Value {
#Id #GeneratedValue(strategy = IDENTITY)
#Getter
private Long id;
public abstract String getType();
}
And the subclass:
#Entity #DiscriminatorValue("D")
#Getter #Setter
public class DecimalValue extends Value {
#Column(name = "DECIMAL_VALUE")
private BigDecimal value;
public String getType() {
return "DECIMAL";
}
}

How are request parameters converted in Spring Data Rest

I have a repository for a mapped class in Spring Data Rest. The mapped class has an enum field:
#Entity
class Run {
#Id
#GeneratedValue
Integer id
Status status
public static enum Status {
IN_PROCESS('inProcess'),
FAILED('failed'),
SUCCESS('success')
String value
Status(String value) {
this.value = value
}
#JsonValue
#Override
public String toString() {
return value
}
}
}
I have declared the following repository for the Run entity:
interface RunRepository extends CrudRepository<Run, Integer> {
public Iterable<Run> findByStatus(#Param('status') Run.Status status)
}
I've implemented a custom Converter for the Run.Status enum, which I've for the sake of simplicity, cut down to the following:
class RunStatusConverter implements Converter<String, Run.Status> {
#Override
Run.Status convert(String s) {
return Run.Status.IN_PROCESS
}
}
This converter has been registered using a #Configuration annotated class, and it works fine when used to convert custom #Controller method parameters, such as in the following:
#Controller
class TestController {
#RequestMapping(path = '/test-status')
public #ResponseBody String testStatus(#RequestParam Run.Status status) {
println 'status: ' + status
return status.value
}
}
When, however, I call the findByStatus method (over HTTP) on the RunRepository class, the converter does not get called and instead I get a org.springframework.core.convert.ConversionFailedException whose underlying cause is the following: java.lang.IllegalArgumentException: No enum constant Run.Status.inProcess.
It seems the custom converter is not getting called when passing parameters to the repository.
How do I get the repository to receive the correctly converted request parameter, when calling exposed repository methods over HTTP?

spring controller does not work when I extend it

i try make a generic controller with some methods, so i don´t need re-writer common codes, but don´t work, why???
#Controller("/home/teste")
public class CtrlTeste extends ControladorGenericoSpring<Assistenciado>
{
public String path;
public CtrlTeste()
{
super(Assistenciado.class);
path = "/home/teste";
setPacoteServico("servico.assistenciado");
setPrefixo("Serv");
setNomeEntidade("Assistênciado");
}
#RequestMapping
public String teste(#RequestParam(value = "id", required= true)Long id, Model model)
{
Assistenciado ass = getServico().buscarPorId(id);
model.addAttribute("assistenciado", ass);
return "/home";
}
}
You're currently specifying the name of the bean. Try with:
#Controller
#RequestMapping("/home/teste")
public class CtrlTeste extends ControladorGenericoSpring<Assistenciado> {
...
}

Resources