Edit 14/08/14 13:29
My next conclusion is that the hal+json format produced from my @RepositoryRestResource CrudRepository is incorrect.
The tutorial (http://spring.io/guides/gs/accessing-data-rest/) shows the output of a hypermedia Rest JPA entity as: (please note there is no "rel" element, and "links" is not an array)
{ "_links" : { "people" : { "href" : "http://localhost:8080/people{?page,size,sort}" } } }
However, the reference docs (http://docs.spring.io/spring-data/rest/docs/1.1.x/reference/html/intro-chapter.html) show that the output should be:
{ "links" : [ { "rel" : "customer", "href" : "http://localhost:8080/customer" }, { "rel" : "profile", "href" : "http://localhost:8080/profile" } }
Does anyone know why this is?
=====================================
Edit 14/08/14: I have taken my debugging a step further. By providing my own implementation of a org.springframework.hateoas.ResourceSupport class, which inspects the json for "_links" rather than "links" I get a step further. The error is:
"Can not deserialize instance of java.util.ArrayList out of START_OBJECT token ..... through reference chain: com.ebs.solas.admin.test.SolicitorDTO[\"_links\"]"
This is because the org.springframework.hateoas.ResourceSupport class seems to require that the links attribute be a json array. And by default the json+hal output produced by Spring Data for a Rest Entity does not produce an array (there are no square brackets):
"_links" : { "self" : { "href" : "http://localhost:9090/solas-admin-data-api/solicitorFirms/Fxxx" }, "solicitors" : { "href" : "http://localhost:9090/solas-admin-data-api/solicitorFirms/Fxxx/solicitor } }
Hopefully someone from the Spring forums could help me here.
==============================================
please see an outline of my Spring Data repository code:
@RepositoryRestResource public interface SolicitorFirmRepository extends CrudRepository<SolicitorFirm, String> { } @Entity @RestResource @Table(name="XXXX", schema = "XXX") public class SolicitorFirm implements Serializable { }
This successfully generates the following hateoas resource:
{ "firmNumber" : "FXXXX", "solicitorType" : "r", "companyName" : "XXXX", "address1" : "XXXX", "address2" : "XXX", "address3" : "XXX", "address4" : null, "phoneNumber" : "XXXXX", "faxNumber" : "XXXXX", "county" : "OY", "_links" : { "self" : { "href" : "http://localhost:9090/solas-admin-data-api/solicitorFirms/XXXX" }, "solicitors" : { "href" : "http://localhost:9090/solas-admin-data-api/solicitorFirms/XXXX/solicitors" } }
HOWEVER, when i define a DTO for clientside/controller use:
import org.springframework.hateoas.ResourceSupport; public class SolicitorFirmDTO extends ResourceSupport { ..... }
and use the following code
RestTemplate rt = new RestTemplate(); String uri = new String("//xxxxx:9090/solas-admin-data-api/solicitors/Sxxxxx"); SolicitorFirmDTO u = rt.getForObject(uri, SolicitorFirmDTO.class, "SXXXX");
I get the following error:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "_links" (class com.ebs.solas.admin.test.SolicitorFirmDTO), not marked as ignorable (7 known properties: xx])
For some reason the json produced by Spring Data Rest adds the entity links under _links
while the HATEOAS resource superclass expects links
?
Can any one help? is this a version issue or do I need some extra configuration to map _links
to links
I have tried MappingJackson2HttpMessageConverter
and various media types application/json+hal
to no avail.
For Spring-boot 1.3.3 the method exchange() for List is working
public void test1() { RestTemplate restTemplate = restTemplate(); ParameterizedTypeReference<PagedResources<User>> responseTypeRef = new ParameterizedTypeReference<PagedResources<User>>() { }; String API_URL = "http://localhost:8080/api/v1/user" ResponseEntity<PagedResources<User>> responseEntity = restTemplate.exchange(API_URL, HttpMethod.GET, (HttpEntity<User>) null, responseTypeRef); PagedResources<User> resources = responseEntity.getBody(); Collection<User> users = resources.getContent(); List<User> userList = new ArrayList<User>(users); System.out.println(userList); } private RestTemplate restTemplate() { ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.registerModule(new Jackson2HalModule()); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.setSupportedMediaTypes(MediaType.parseMediaTypes("application/hal+json")); converter.setObjectMapper(mapper); List<HttpMessageConverter<?>> converterList = new ArrayList<HttpMessageConverter<?>>(); converterList.add(converter); RestTemplate restTemplate = new RestTemplate(converterList); return restTemplate; }
Also with mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
use @JsonIgnoreProperties(ignoreUnknown = true)
on every Entity:
@Entity @JsonIgnoreProperties(ignoreUnknown = true) public class User { ... }
thanks for your response.
In answer to your questions,
1) I believe that my input and output are both HAL. You will see from my original post that the json produced from my @RepositoryRestResource is HAL (notice it contains ref links to itself and associated entities):
{ "firmNumber" : "Fxx", "solicitorType" : "r", "companyName" : "xxx", "address1" : "xxx,", "address2" : "xx,", "address3" : "xxx,", "_links" : { "self" : { "href" : "http://localhost:9090/solas-admin-data-api/solicitorFirms/Fxx" }, "solicitors" : { "href" : "http://localhost:9090/solas-admin-data-api/solicitorFirms/Fxx/solicitors } } }
However the reference links are under the attribute name "_links", but the RestSupport class on the client side does not seem to expect any _underscore, it only seems to search for "links"
2) yes i have specified @EnableHypermediaSupport(type = HypermediaType.HAL),
please see below for my full configuration is as follows (javaconfig):
@Configuration @ComponentScan("com.ebs.solas.admin") @EnableJpaRepositories("com.ebs.solas.admin") @EnableTransactionManagement @Import(RepositoryRestMvcConfiguration.class) class ApplicationConfig { @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("com.ibm.db2.jcc.DB2Driver"); dataSource.setUrl("jdbc:db2://xxxx:52001/xxxx"); dataSource.setUsername( "xxx" ); dataSource.setPassword( "xxx" ); return dataSource; } @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory() { HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); vendorAdapter.setDatabase(Database.DB2); vendorAdapter.setGenerateDdl(false); LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); factory.setJpaVendorAdapter(vendorAdapter); factory.setPackagesToScan("com.ebs.solas.admin"); factory.setDataSource(dataSource()); return factory; } @Bean public PlatformTransactionManager transactionManager() { JpaTransactionManager txManager = new JpaTransactionManager(); txManager.setEntityManagerFactory(entityManagerFactory().getObject()); return txManager; } } public class RestWebApplicationInitializer implements WebApplicationInitializer { public void onStartup(ServletContext context) throws ServletException { AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext(); rootContext.register(ApplicationConfig.class); context.addListener(new ContextLoaderListener(rootContext)); RepositoryRestDispatcherServlet exporter = new RepositoryRestDispatcherServlet(); ServletRegistration.Dynamic reg = context.addServlet("exporter", exporter); reg.setLoadOnStartup(1); reg.addMapping("/*"); } } @Configuration @ComponentScan("com.ebs.solas.admin") @EnableWebMvc @EnableHypermediaSupport(type = HypermediaType.HAL) class WebMVCConfiguration extends WebMvcConfigurationSupport { @Override public void configureContentNegotiation(ContentNegotiationConfigurer c) { c.defaultContentType(MediaType.APPLICATION_JSON); } @Bean public MultipartResolver multipartResolver() { return new StandardServletMultipartResolver(); } }
My RestController also specifies that the RestTemplate should use hal+json message conversion format, see below
@RestController public class TestController { @RequestMapping(value="/test", method=RequestMethod.GET, produces={"application/hal+json"}) @ResponseStatus(HttpStatus.OK) public SolicitorDTO doTest() { ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new Jackson2HalModule()); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.setSupportedMediaTypes(org.springframework.http.MediaType.parseMediaTypes("application/hal+json")); converter.setObjectMapper(mapper); RestTemplate rt = new RestTemplate(); rt.getMessageConverters().add(converter); String uri = new String("http://localhost:9090/solas-admin-data-api/solicitors/{id}"); SolicitorDTO u = rt.getForObject(uri, SolicitorDTO.class, "Sxxxxx"); return u; } }
Thanks for your help, appdJava