Resolving entity URI in custom controller (Spring HATEOAS)
Asked Answered
I

7

19

I have a project based on spring-data-rest and also it has some custom endpoints.

For sending POST data I'm using json like

{
 "action": "REMOVE",
 "customer": "http://localhost:8080/api/rest/customers/7"
}

That is fine for spring-data-rest, but does not work with a custom controller.

for example:

public class Action {
    public ActionType action;
    public Customer customer;
}

@RestController
public class ActionController(){
  @Autowired
  private ActionService actionService;

  @RestController
  public class ActionController {
  @Autowired
  private ActionService actionService;

  @RequestMapping(value = "/customer/action", method = RequestMethod.POST)
  public ResponseEntity<ActionResult> doAction(@RequestBody Action action){
    ActionType actionType = action.action;
    Customer customer = action.customer;//<------There is a problem
    ActionResult result = actionService.doCustomerAction(actionType, customer);
    return ResponseEntity.ok(result);
  }
}

When I call

curl -v -X POST -H "Content-Type: application/json" -d '{"action": "REMOVE","customer": "http://localhost:8080/api/rest/customers/7"}' http://localhost:8080/customer/action

I have an answer

{
"timestamp" : "2016-05-12T11:55:41.237+0000",
"status" : 400,
"error" : "Bad Request",
"exception" : "org.springframework.http.converter.HttpMessageNotReadableException",
"message" : "Could not read document: Can not instantiate value of type [simple type, class model.user.Customer] from String value ('http://localhost:8080/api/rest/customers/7'); no single-String constructor/factory method\n at [Source: java.io.PushbackInputStream@73af10c6; line: 1, column: 33] (through reference chain: api.controller.Action[\"customer\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class logic.model.user.Customer] from String value ('http://localhost:8080/api/rest/customers/7'); no single-String constructor/factory method\n at [Source: java.io.PushbackInputStream@73af10c6; line: 1, column: 33] (through reference chain: api.controller.Action[\"customer\"])",
"path" : "/customer/action"
* Closing connection 0
}

bacause case spring can not convert a URI to a Customer entity.

Is there any way to use spring-data-rest mechanism for resolving entities by their URIs?

I have only one idea - to use custom JsonDeserializer with parsing URI for extracting entityId and making a request to a repository. But this strategy does not help me if I have URI like "http://localhost:8080/api/rest/customers/8/product" in that case I do not have product.Id value.

Incendiarism answered 12/5, 2016 at 12:7 Comment(5)
I couldn't find it, but I've posted on this topic before. Unfortunately, there's currently no automatic way to extract an entity from a link in Spring HATEOAS, and for at least two years the maintainers have ignored the need to understand incoming URLs in the request.Leaved
Request and controller don't seem to be RESTful. And http://localhost:8080/api/rest/customers/8/product is not a URI. Instead of trying to solve this particular problem I strongly recommend to redesign your API.Quirk
@zeroflagL, you are right, but we already have workable service with more than 20 rest resources that obey hateoas rules, we have clients that use it, and now we have to add two more custom endpoints. So I think it is logically to use same rules for all resources in our service.Incendiarism
I hope (because we already have spring-data-rest) find a way to use SDR flow for entity resolvingIncendiarism
I fully agree that it's a bad idea to completely rewrite a working application. This particular endpoint, however, does not (seem to) "obey hateoas rules". The approach you suggested is a good one and, as I said, you don't have to worry about http://localhost:8080/api/rest/customers/8/product, because it's not a URI. You never will use that as a reference to a product.Quirk
P
8

I have been having the same problem too for really long time now and solved it the following way. @Florian was on the right track and thanks to his suggestion I found a way to make the conversion work automatically. There are several pieces needed:

  1. A conversion service to enable the conversion from a URI to an entity (leveraging the UriToEntityConverter provided with the framework)
  2. A deserializer to detect when it is appropriate to invoke the converter (we don't want to mess up with the default SDR behavior)
  3. A custom Jackson module to push everything to SDR

For point 1 the implementation can be narrowed to the following

import org.springframework.context.ApplicationContext;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.rest.core.UriToEntityConverter;
import org.springframework.format.support.DefaultFormattingConversionService;

public class UriToEntityConversionService extends DefaultFormattingConversionService {

   private UriToEntityConverter converter;

   public UriToEntityConversionService(ApplicationContext applicationContext, PersistentEntities entities) {
      new DomainClassConverter<>(this).setApplicationContext(applicationContext);

       converter = new UriToEntityConverter(entities, this);

       addConverter(converter);
   }

   public UriToEntityConverter getConverter() {
      return converter;
   }
}

For point 2 this is my solution

import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator;
import your.domain.RootEntity; // <-- replace this with the import of the root class (or marker interface) of your domain
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.rest.core.UriToEntityConverter;
import org.springframework.util.Assert;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Optional;


public class RootEntityFromUriDeserializer extends BeanDeserializerModifier {

   private final UriToEntityConverter converter;
   private final PersistentEntities repositories;

   public RootEntityFromUriDeserializer(PersistentEntities repositories, UriToEntityConverter converter) {

       Assert.notNull(repositories, "Repositories must not be null!");
       Assert.notNull(converter, "UriToEntityConverter must not be null!");

       this.repositories = repositories;
       this.converter = converter;
   }

   @Override
   public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {

       PersistentEntity<?, ?> entity = repositories.getPersistentEntity(beanDesc.getBeanClass());

       boolean deserializingARootEntity = entity != null && RootEntity.class.isAssignableFrom(entity.getType());

       if (deserializingARootEntity) {
           replaceValueInstantiator(builder, entity);
       }

       return builder;
   }

   private void replaceValueInstantiator(BeanDeserializerBuilder builder, PersistentEntity<?, ?> entity) {
      ValueInstantiator currentValueInstantiator = builder.getValueInstantiator();

       if (currentValueInstantiator instanceof StdValueInstantiator) {

          EntityFromUriInstantiator entityFromUriInstantiator =
                new EntityFromUriInstantiator((StdValueInstantiator) currentValueInstantiator, entity.getType(), converter);

          builder.setValueInstantiator(entityFromUriInstantiator);
       }
   }

   private class EntityFromUriInstantiator extends StdValueInstantiator {
      private final Class entityType;
      private final UriToEntityConverter converter;

      private EntityFromUriInstantiator(StdValueInstantiator src, Class entityType, UriToEntityConverter converter) {
         super(src);
         this.entityType = entityType;
         this.converter = converter;
      }

      @Override
      public Object createFromString(DeserializationContext ctxt, String value) throws IOException {
         URI uri;
         try {
            uri = new URI(value);
         } catch (URISyntaxException e) {
            return super.createFromString(ctxt, value);
         }

         return converter.convert(uri, TypeDescriptor.valueOf(URI.class), TypeDescriptor.valueOf(entityType));
      }
   }
}

Then for point 3, in the custom RepositoryRestConfigurerAdapter,

public class MyRepositoryRestConfigurer extends RepositoryRestConfigurerAdapter {
   @Override
   public void configureJacksonObjectMapper(ObjectMapper objectMapper) {
      objectMapper.registerModule(new SimpleModule("URIDeserializationModule"){

         @Override
         public void setupModule(SetupContext context) {
            UriToEntityConverter converter = conversionService.getConverter();

            RootEntityFromUriDeserializer rootEntityFromUriDeserializer = new RootEntityFromUriDeserializer(persistentEntities, converter);

            context.addBeanDeserializerModifier(rootEntityFromUriDeserializer);
         }
      });
   }
}

This works smoothly for me and does not interfere with any conversion from the framework (we have many custom endpoints). In the point 2 the intent was to enable the instantiation from a URI only in cases where:

  1. The entity being deserialized is a root entity (so no properties)
  2. The provided string is an actual URI (otherwise it just falls back to the default behavior)
Peculation answered 29/10, 2016 at 18:22 Comment(9)
Hi, could you post the entire code of those 3 classes? I've some issues trying to compile that code and fixes imports. ThanksGaze
Hi, I'm not sure I can post the full code (if there actually is more code), I need to double-check. In the meantime, do I understand correctly that having the correct imports (and maybe dependency versions) will likely fix your problem?Peculation
Yes I think so. I have the same problem in my applicationGaze
I edited the post again, please check if this works for you. I realized I had forgotten to mention that RootEntity is whatever is at the root of your domain structure. We used to have IDs and other stuff used by Hibernate in it but, for the way I used it, it might be a marker interface. Please let me know if I need to improve anything else and if it worked for you as well, otherwise I can help. I bet we can tame it again :)Peculation
This answer does not appear to validate that the URI being supplied matches the root uri of the api server or that the uri is a valid path for the class of entity you're trying to convert into?Sunnysunproof
@adamp Shouldn't the request fail anyway if the URI is somehow invalid? If I remember correctly (I moved to another project a year ago), this should be taken care of by the UriToEntityConverter and this code provides a way to hook into the same mechanism. Otherwise can you please rephrase your question? Because I'm not sure i understand the problemPeculation
@LuigiCristalli Based on the code above, it does not appear to do so, at least in the current version of SDR. It will happily accept any junk valid uri that is in the /{anything}/{id} format and look for an entity with that id in your table, or just return null.Sunnysunproof
In MyRepositoryRestConfigurer I don't get what is persistentEntities and how to get conversionService. Someone has an advice on that? ThanksGaze
Does anyone have a full example of this? There are many things I don't get to work in these snippets.Alimentation
R
2

This is more of an side note instead of a real answer, but a while ago I managed to copy&paste myself a class to resolve entities from an URL by using the methods used in SDR (just more crude). There probably is a much better way, but until then, perhaps this helps...

@Service
public class EntityConverter {

    @Autowired
    private MappingContext<?, ?> mappingContext;

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired(required = false)
    private List<RepositoryRestConfigurer> configurers = Collections.emptyList();

    public <T> T convert(Link link, Class<T> target) {

        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();

        PersistentEntities entities = new PersistentEntities(Arrays.asList(mappingContext));
        UriToEntityConverter converter = new UriToEntityConverter(entities, conversionService);
        conversionService.addConverter(converter);
        addFormatters(conversionService);
        for (RepositoryRestConfigurer configurer : configurers) {
            configurer.configureConversionService(conversionService);
        }

        URI uri = convert(link);
        T object = target.cast(conversionService.convert(uri, TypeDescriptor.valueOf(target)));
        if (object == null) {
            throw new IllegalArgumentException(String.format("%s '%s' was not found.", target.getSimpleName(), uri));
        }
        return object;
    }

    private URI convert(Link link) {
        try {
            return new URI(link.getHref());
        } catch (Exception e) {
            throw new IllegalArgumentException("URI from link is invalid", e);
        }
    }

    private void addFormatters(FormatterRegistry registry) {

        registry.addFormatter(DistanceFormatter.INSTANCE);
        registry.addFormatter(PointFormatter.INSTANCE);

        if (!(registry instanceof FormattingConversionService)) {
            return;
        }

        FormattingConversionService conversionService = (FormattingConversionService) registry;

        DomainClassConverter<FormattingConversionService> converter = new DomainClassConverter<FormattingConversionService>(
                conversionService);
        converter.setApplicationContext(applicationContext);
    }

}

And yes, it's likely that parts of this class are simply useless. In my defense, it was just a short hack and I never got around to actually need it, because I found other problems first ;-)

Rhoda answered 12/5, 2016 at 15:30 Comment(7)
Works for me, but too bad it doesn't work for child entities (e.g. http://localhost:8080/contacts/1/childEntity) I searched around and found nothing so far. Also, I had to change the UriToEntityConverter instantiation to: Repositories repositories = new Repositories(applicationContext); UriToEntityConverter converter = new UriToEntityConverter( new PersistentEntities(Collections.singleton(mappingContext)), new DefaultRepositoryInvokerFactory(repositories), repositories);Vacation
@Vacation how did you manage to make it work? I added the service but how do you interconnect it with Spring? ThanksGaze
@Gaze Just autowire it. @Autowired private EntityConverter entityConverter;Vacation
@Vacation ok. But how do you use that in Spring custom controller?Gaze
@Gaze Forget about what I said earlier. That was for using it manually. For using it in controllers, I'll have to post an answer because it's a bit more complex. Check again in a few minutes.Vacation
@Vacation Thanks very muchGaze
How do you get MappingContext? I get bean not found?Poulenc
S
2

For HAL with @RequestBody use Resource<T> as method parameter instead entity Action to allow convert related resources URIs

public ResponseEntity<ActionResult> doAction(@RequestBody Resource<Action> action){
Sherylsheryle answered 18/3, 2017 at 20:43 Comment(5)
What do you return from that method?Boatsman
Sorry!, It was a mistake. I fixed it to return as the original sample.Sherylsheryle
If a pass only the URI, in a text/uri-list it do not convert to entity. To be more clear @RequestBody Resource<EntityType> or Resources<EntityType> passing in body http://localhost:8080/api/entityType/1 result in empty resources.Deoxidize
"Resources is for a collection while Resource is for a single item. These types can be combined. If you know the links for each item in a collection, use Resources<Resource<String>> (or whatever the core domain type is). This lets you assemble links for each item as well as for the whole collection." See more at Spring Data REST Reference DocSherylsheryle
This only seems to work for me if the passed class (Action in the example above) is an Entity, if it's just a POJO I still get the error.Undercoating
Q
2

Unfortunately UriToEntityConverter(A Generic Converter that can convert a URI into an entity) which use Spring Data REST are not exported as a Bean or as a Service.

So we can not @Autowired it directly but it registered as Converter in Default Formatting Conversion Service.

Thus we manage to @Autowired Default Formatting Conversion Service and use them to convert a URI into an entity, for example:

@RestController
@RequiredArgsConstructor
public class InstanceController {

    private final DefaultFormattingConversionService formattingConversionService;

    @RequestMapping(path = "/api/instances", method = {RequestMethod.POST})
    public ResponseEntity<?> create(@RequestBody @Valid InstanceDTO instanceDTO) { // get something what you want from request

        // ...

        // extract URI from any string what you want to process
        final URI uri = "..."; // http://localhost:8080/api/instances/1
        // convert URI to Entity
        final Instance instance = formattingConversionService.convert(uri, Instance.class); // Instance(id=1, ...)

        // ...

    }

}
Quillen answered 8/5, 2019 at 3:14 Comment(0)
S
1

I can't believe it. After wrapping my head around this for MONTH(!) I managed to SOLVE THIS!

Some words of introduction:

Spring HATEOAS uses URIs as references to entities. And it provides great support to obtain these URI Links for a given entity. For example, when a client requests an entity that references other child entities, then the client will receive those URIs. Nice to work with.

GET /users/1
{ 
  "username": "foobar",
  "_links": {
     "self": {
       "href": "http://localhost:8080/user/1"  //<<<== HATEOAS Link
      }
  }
}

The REST client only works with those uris. The REST client MUST NOT know the structure of these URIs. The REST client does not know, that there is a DB internal ID at the end of the URI string.

So far so good. BUT spring data HATEOAS does not offer any functionality to convert an URI back to the corresponding entity (loaded from the DB). Everyone needs that in custom REST controllers. (See question above)

Think of an example where you want to work with a user in a custom REST controller. The client would send this request

POST /checkAdress
{
   user: "/users/1"
   someMoreOtherParams: "...",
   [...]
}

How shall the custom REST controller deserialize from the (String) uri to the UserModel? I found a way: You must configure the Jackson deserialization in your RepositoryRestConfigurer:

RepositoryRestConfigurer.java

public class RepositoryRestConfigurer extends RepositoryRestConfigurerAdapter {
@Autowired
  UserRepo userRepo;

  @Override
  public void configureJacksonObjectMapper(ObjectMapper objectMapper) {
    SimpleModule module = new SimpleModule();
    module.addDeserializer(UserModel.class, new JsonDeserializer<UserModel>() {
    @Override
        public UserModel deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            String uri = p.getValueAsString();
            //extract ID from URI, with regular expression (1)
            Pattern regex = Pattern.compile(".*\\/" + entityName + "\\/(\\d+)");
            Matcher matcher = regex.matcher(uri);
            if (!matcher.matches()) throw new RuntimeException("This does not seem to be an URI for an '"+entityName+"': "+uri);
            String userId = matcher.group(1);
            UserModel user = userRepo.findById(userId)   
              .orElseThrow(() -> new RuntimeException("User with id "+userId+" does not exist."))
            return user;
        }
    });
    objectMapper.registerModule(module);
}

}

(1) This string parsing is ugly. I know. But it's just the inverse of org.springframework.hateoas.EntityLinks and its implementations. And the author of spring-hateos stubbornly refuses to offer utility methods for both directions.

Solvable answered 22/10, 2018 at 22:54 Comment(3)
One funny side remark: If you just simply provide a constructor from String (as the error message would suppose, then spring-data-rest actually calls that constructor. But how to load an entity from within that constructor in your model layer. That would be even more ugly. public UserModle(String uri) { ... userRepo.findById(parsedId) ... }Solvable
Just for reference: Can also be implemented with @JsonComonent docs.spring.io/spring-boot/docs/current/reference/html/…Solvable
Be careful with @JsonComponent. When you anotate your EntityDeserializer<T> with @JsonComponent it will ALWAYS be used. This means it will also be used for the standard spring data rest endpoints. If you want custom deserialization only for your custom Rest Controllers then you can add @JsonDeserialize(using=MyCustomModelDeserializer.class) to the attribute in your entitty.Solvable
V
0

I arrived at the following solution. It's a bit hackish, but works.

First, the service to convert URIs into entities.

EntityConverter

import java.net.URI;
import java.util.Collections;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.geo.format.DistanceFormatter;
import org.springframework.data.geo.format.PointFormatter;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.repository.support.DefaultRepositoryInvokerFactory;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.UriToEntityConverter;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.hateoas.Link;
import org.springframework.stereotype.Service;

@Service
public class EntityConverter {

    @Autowired
    private MappingContext<?, ?> mappingContext;

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired(required = false)
    private List<RepositoryRestConfigurer> configurers = Collections.emptyList();

    public <T> T convert(Link link, Class<T> target) {

        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();

        Repositories repositories = new Repositories(applicationContext);
        UriToEntityConverter converter = new UriToEntityConverter(
            new PersistentEntities(Collections.singleton(mappingContext)),
            new DefaultRepositoryInvokerFactory(repositories),
            repositories);

        conversionService.addConverter(converter);
        addFormatters(conversionService);
        for (RepositoryRestConfigurer configurer : configurers) {
            configurer.configureConversionService(conversionService);
        }

        URI uri = convert(link);
        T object = target.cast(conversionService.convert(uri, TypeDescriptor.valueOf(target)));
        if (object == null) {
            throw new IllegalArgumentException(String.format("registerNotFound", target.getSimpleName(), uri));
        }
        return object;
    }

    private URI convert(Link link) {
        try {
            return new URI(link.getHref().replace("{?projection}", ""));
        } catch (Exception e) {
            throw new IllegalArgumentException("invalidURI", e);
        }
    }

    private void addFormatters(FormatterRegistry registry) {

        registry.addFormatter(DistanceFormatter.INSTANCE);
        registry.addFormatter(PointFormatter.INSTANCE);

        if (!(registry instanceof FormattingConversionService)) {
            return;
        }

        FormattingConversionService conversionService = (FormattingConversionService) registry;

        DomainClassConverter<FormattingConversionService> converter = new DomainClassConverter<FormattingConversionService>(
                conversionService);
        converter.setApplicationContext(applicationContext);
    }
}

Second, a component to be able to use the EntityConverter outside of the Spring context.

ApplicationContextHolder

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class ApplicationContextHolder implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    public static ApplicationContext getContext() {
        return context;
    }
}

Third, entity constructor that takes another entity as input.

MyEntity

public MyEntity(MyEntity entity) {
    property1 = entity.property1;
    property2 = entity.property2;
    property3 = entity.property3;
    // ...
}

Fourth, entity constructor that takes a String as input, which should be the URI.

MyEntity

public MyEntity(String URI) {
    this(ApplicationContextHolder.getContext().getBean(EntityConverter.class).convert(new Link(URI.replace("{?projection}", "")), MyEntity.class));
}

Optionally, I have moved part of the code above to an Utils class.

I arrived at this solution by looking at the error message from the question post, which I was getting as well. Spring doesn't know how to construct an object from a String? I'll show it how...

Like a said in a comment, however, doesn't work for nested entities' URIs.

Vacation answered 11/7, 2018 at 11:9 Comment(0)
B
0

My solution will be some compact. Not sure that it can be useful for all cases but for simple relation like .../entity/{id} it could parse. I've tested it on SDR & Spring Boot 2.0.3.RELEASE

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.repository.support.RepositoryInvokerFactory;
import org.springframework.data.rest.core.UriToEntityConverter;
import org.springframework.hateoas.Link;
import org.springframework.stereotype.Service;

import java.net.URI;
import java.util.Collections;

@Service
public class UriToEntityConversionService {

    @Autowired
    private MappingContext<?, ?> mappingContext; // OOTB

    @Autowired
    private RepositoryInvokerFactory invokerFactory; // OOTB

    @Autowired
    private Repositories repositories; // OOTB

    public <T> T convert(Link link, Class<T> target) {

        PersistentEntities entities = new PersistentEntities(Collections.singletonList(mappingContext));
        UriToEntityConverter converter = new UriToEntityConverter(entities, invokerFactory, repositories);

        URI uri = convert(link);
        Object o = converter.convert(uri, TypeDescriptor.valueOf(URI.class), TypeDescriptor.valueOf(target));
        T object = target.cast(o);
        if (object == null) {
            throw new IllegalArgumentException(String.format("%s '%s' was not found.", target.getSimpleName(), uri));
        }
        return object;
    }

    private URI convert(Link link) {
        try {
            return new URI(link.getHref());
        } catch (Exception e) {
            throw new IllegalArgumentException("URI from link is invalid", e);
        }
    }
}

Usage:

@Component
public class CategoryConverter implements Converter<CategoryForm, Category> {

    private UriToEntityConversionService conversionService;

    @Autowired
    public CategoryConverter(UriToEntityConversionService conversionService) {
            this.conversionService = conversionService;
    }

    @Override
    public Category convert(CategoryForm source) {
        Category category = new Category();
        category.setId(source.getId());
        category.setName(source.getName());
        category.setOptions(source.getOptions());

        if (source.getParent() != null) {
            Category parent = conversionService.convert(new Link(source.getParent()), Category.class);
            category.setParent(parent);
        }
        return category;
    }
}

Request JSON like:

{
    ...
    "parent": "http://localhost:8080/categories/{id}",
    ...
}
Blackfish answered 1/8, 2018 at 22:40 Comment(4)
How do you add/register/configure your CategoryConverter? So that it used by your REST controller?Solvable
Hi, @Robert, I've replaced OOTB DataRestController by my own implementation by using a set of @BasePathAwareController & @RepositoryRestController annotated RestCategoryController, and placed my converter thereBlackfish
The drawback there is you must specify "/categories/..." prefix in all methods in the given controller like @RequestMapping(path = "categories", method = POST, consumes = MediaType.APPLICATION_JSON_VALUE) to create entity for example. Non-overriden SDR mappings will still work aside.Blackfish
How do you get MappingContext? It says bean not found?Poulenc

© 2022 - 2024 — McMap. All rights reserved.