0

I am trying to support XML responses for my Spring HATEOAS based application. JSON responses work fine as well as XML for a single resource. The problem starts with the list of the resources. Spring MVC controller cannot serialize the list built with help of ResourceAssemblerSupport derived class. The controller throws "org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation" for the curl command

curl -k -i -H "Accept:application/xml" -H "Media-Type:application/xml" -X GET http://127.0.0.1:8080/admin/roles*

My HATEOAS resource is a wrapper around entity class:

@XmlRootElement
@XmlSeeAlso(RoleModel.class)
public class RoleResource  extends ResourceSupport {
    public RoleModel role;

}

The controller is simple:

@RequestMapping(method = RequestMethod.GET)
   public @ResponseBody HttpEntity<List<RoleResource>> getAllRoles() 
        throws ObjectAccessException, ObjectNotFoundException {
    List<RoleModel> resp = rolesManagement.getRoles();

    return new ResponseEntity<List<RoleResource>>(roleResourceAssembler.toResources(resp),
            HttpStatus.OK);
}

Resource assembler class:

@Configuration
public class RoleResourceAssembler extends ResourceAssemblerSupport<RoleModel, RoleResource> {

public RoleResourceAssembler() {
    super(RolesRestController.class, RoleResource.class);
}

@Bean 
public RoleResourceAssembler roleResourceAssembler(){
   return new RoleResourceAssembler();
}

@Override
public RoleResource toResource(RoleModel role) {
    RoleResource res = instantiateResource(role); 
    res.role = role;
    try {
        res.add(linkTo(methodOn(RolesRestController.class).getRole(role.getRoleId())).withSelfRel());
    } catch (ObjectAccessException | ObjectNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return res;
}

}

When I avoid ResourceAssemblerSupport and build my resources manually like this:

@XmlRootElement
@XmlSeeAlso(RoleModel.class)
public class RolesList {
    private List<Resource<RoleModel>> roles;

...
}
@RequestMapping(method = RequestMethod.GET)
public @ResponseBody HttpEntity<RolesList> getAllRoles() 
        throws ObjectAccessException, ObjectNotFoundException {
    List<RoleModel> resp = rolesManagement.getRoles();

    List<Resource<RoleModel>> roles =new ArrayList<>(); 
    for (RoleModel model: resp) {
        Resource<RoleModel> res =  new Resource<RoleModel>(model);
        res.add(linkTo(methodOn(RolesRestController.class).getRole(model.getRoleId())).withSelfRel());
        roles.add(res);
    }
    RolesList list = new RolesList();
    list.setRoles(roles);

    return new ResponseEntity<RolesList>(list,
            HttpStatus.OK);
}

XML serialization works. I guess I could avoid using resource assembler and build my resources manually, but that makes the code not as clean and modular. I wonder if it is still possible to use ResourceAssemblerSupport as resource builder and return the list of resources as XML

MrkK
  • 873
  • 3
  • 12
  • 22
  • Another strange thing is that a single resource generated via ResourceAssemblerSupport is serialized as atom and not as plain XML: system administratorADMIN – MrkK Aug 18 '14 at 14:06

3 Answers3

0

A resource assembler is for converting one pojo/entity/whatever to 1 hateoas resource. You are trying to convert a list to a list.

If you assembler was

public class RoleResourceAssembler extends ResourceAssemblerSupport<List<RoleModel>, RolesResource> {

and RolesResource was something that you wanted...it should work.

However i would suggest you look at using the PagedResourceAssembler which takes a page of "things" and uses an assembler to create a page of resources. The page can be the full collection or just a page in the collection. Here's a simple one for categories:

public HttpEntity<PagedResources<CategoryResource>> categories(
    PagedResourcesAssembler<Category> pageAssembler,
    @PageableDefault(size = 50, page = 0) Pageable pageable
){

    Page<Category> shopByCategories = categoryService.findCategories(pageable);        

    PagedResources<CategoryResource> r = pageAssembler.toResource(shopByCategories,
            this.categoryAssembler);

    return new ResponseEntity<PagedResources<CategoryResource>>(r, HttpStatus.OK);
}

But..i've had some problems with jackson marshaling the PagedResources as XML...works fine for JSON.

Chris DaMour
  • 3,650
  • 28
  • 37
  • ResourceAssemblerSupport has two methods: toResource() and toResources(). The latter ones takes an Iterable as an argument and returns list of resources. I thought that is the one I should use. – MrkK Aug 18 '14 at 19:05
  • You are correct..i missed the nuance of ResouseAssembler vs ResourceAssemblerSupport. Returning a List of resources feels weird, much better in my opinion to return a resource that embeds other resources. – Chris DaMour Aug 18 '14 at 19:50
0

It seems that with ResourceAssemblerSupport it is impossible to marshal list of HATEOAS resources to XML. The reason is because the list of resources returned by toResources() method of the class extending ResourceAssemblerSupport has no @XmlRootElement and JAXB fails to marshal it. I had to create classes like

 @XmlRootElement
    public class Roles {    
    private List<RoleResource> roleResource;
    ....
}

@XmlRootElement
public class RoleResource  extends ResourceSupport {
    private RoleModel role;
    ...
}

and manually build my resource list. The same problem occurred when I tried to use to use Spring HATEOAS resource wrapper like

Resource<RoleModel> resource = new Resource<>();

Since Spring's Resource class is not annotated with @XmlRootElement REST controller is unable to marshal it to XML

MrkK
  • 873
  • 3
  • 12
  • 22
  • So, did this solve your problem? Or do the classes still not marshal correctly? – woemler Oct 17 '14 at 18:09
  • @willOEM No I did not. The problem lies not with ResourceAssemblerSupport, but rather with the fact that Java's List is not handled by JAXB since it is not annotated with XmlRootElement. Since toResources() of ResourceAssemblerSupport returns the List of resources JAXB fails to marshal it. My solution was to not to use ResourceAssemblerSupport and manually build my resources using my list class which does have XmlRootElement – MrkK Oct 20 '14 at 12:19
  • Thanks. I got your solution to work for me. FYI, I also found that XStream will correctly marshal `Resource` and `Resources` objects using resource assemblers without additional configuration or annotation, but they are still in atom format. – woemler Oct 20 '14 at 13:20
  • @willOEM How did you get atom with XStream? For me it unmarshalls to regular XML. HATEOS XML should be atom according to the spec. – MrkK Nov 06 '14 at 18:22
0

If your only requirement is to generate links with org.springframework.hateoas.Link and marshall as XML, the following may help.

Add a Link item to your model class.

@XmlRootElement(name="Role")
public class Roles
{
   Link link;
   <your Roles content>
   ...
}

Wrap the base class in a list in order to provide a base tag which supports XML marshalling.

@XmlRootElement(name="Roles")
public class RolesList
{
   private List<Roles> rolesList;
   ...
   <constructors>
   ...
   @XmlElement(name="Role")
   public List<Roles> getRolesList()
   {
      return rolesList;
   }

   <set/get/add methods>
}

Your controller code becomes (roughly):

@RequestMapping(method = RequestMethod.GET)
public @ResponseBody HttpEntity<RolesList> getAllRoles() 
   throws ObjectAccessException, ObjectNotFoundException
{
   RolesList resp = new RolesList(rolesManagement.getRoles());
   for (Roles r: resp)
   {
       r.setLink(linkTo(methodOn(RolesRestController.class).getRole(model.getRoleId())).withSelfRel());
   }
   return new ResponseEntity<RolesList>(resp,HttpStatus.OK);
}