Note that there are some explanatory texts on larger screens.

plurals
  1. POSpring MVC 3: return a Spring-Data Page as JSON - issue with PagedResourcesAssembler
    text
    copied!<p>So, I'm fairly new to spring and to java in general</p> <p>What I try to do is to have on the same rendered view the form to post data to filter the result list displayed under the form.</p> <p>I have a simple domain class as follows:</p> <pre><code>@Entity @Table(name = "SEC_PERSON") public class Person { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_SEC_PERSON") @SequenceGenerator(name = "SEQ_SEC_PERSON", sequenceName = "SEQ_SEC_PERSON") @Column(name = "ID") private Long Id; @Column(name = "CODE", nullable = false) private String code; @Column(name = "FIRSTNAME", nullable = false) private String firstname; @Column(name = "SURNAME", nullable = false) private String surname; @Column(name = "CREATIONDATE") private DateTime creationDate; //getters and setters </code></pre> <p>a DTO because I want my domain to be decoupled from my presentation</p> <pre><code>public class PersonDTO { private Long id; @NotEmpty private String code; @NotEmpty private String firstname; @NotEmpty private String surname; private DateTime creationDate; public PersonDTO() { } //getters and setters </code></pre> <p>a repository extending Jpa and QueryDsl</p> <pre><code>public interface PersonRepository extends JpaRepository&lt;Person, Long&gt;, QueryDslPredicateExecutor&lt;Person&gt; {} </code></pre> <p>a data access class for my search that is null safe (thanks guava) and its equivalent not null safe</p> <p>The person Criteria:</p> <pre><code>public class PersonCriteria { private String code; private String surname; private String firstname; private LocalDate creationDateFrom; private LocalDate creationDateTo; //getters and setters } </code></pre> <p>The null safe version</p> <pre><code>public class NullSafePersonCriteria { private final PersonCriteria personCriteria; public NullSafePersonCriteria(final PersonCriteria personCriteria) { checkArgument(personCriteria != null); this.personCriteria = personCriteria; } public Optional&lt;String&gt; getCode() { return Optional.fromNullable(this.personCriteria.getCode()); } public Optional&lt;String&gt; getSurname() { return Optional.fromNullable(this.personCriteria.getSurname()); } public Optional&lt;String&gt; getFirstname() { return Optional.fromNullable(this.personCriteria.getFirstname()); } public Optional&lt;LocalDate&gt; getCreationDateFrom() { return Optional.fromNullable(this.personCriteria.getCreationDateFrom()); } public Optional&lt;LocalDate&gt; getCreationDateTo() { return Optional.fromNullable(this.personCriteria.getCreationDateTo()); } </code></pre> <p>My predicate to search</p> <pre><code>public class PersonPredicates { public static Predicate PersonLitstQuery(final PersonCriteria personCriteria) { final QPerson person = QPerson.person; final NullSafePersonCriteria nsPersonCriteria = new NullSafePersonCriteria(personCriteria); BooleanExpression criteria = QPerson.person.isNotNull(); if (nsPersonCriteria.getCode().isPresent()) { criteria = criteria.and(person.code.matches(nsPersonCriteria.getCode().get())); } if (nsPersonCriteria.getSurname().isPresent()) { criteria.and(person.surname.startsWithIgnoreCase(nsPersonCriteria.getSurname().get())); } if (nsPersonCriteria.getFirstname().isPresent()) { criteria.and(person.firstname.startsWithIgnoreCase(nsPersonCriteria.getFirstname().get())); } if ((nsPersonCriteria.getCreationDateFrom().isPresent()) &amp;&amp; (nsPersonCriteria.getCreationDateTo().isPresent())) { criteria.and(person.creationDate.between(nsPersonCriteria.getCreationDateFrom().get().toDateTimeAtStartOfDay(), nsPersonCriteria.getCreationDateTo().get().toDateTimeAtStartOfDay())); } return criteria; } </code></pre> <p>My Service implementation is as follows:</p> <pre><code>@Service public class PersonServiceImpl implements PersonService{ @Transactional(readOnly = true) @Override public Page&lt;PersonDTO&gt; search(final PersonCriteria criteria, final int pageIndex) { LOGGER.debug("Searching person with set of criterias"); return new PersonPage(this.mapper.map(Lists.newArrayList(this.personRepository.findAll(PersonLitstQuery(criteria))), PersonDTO.class), constructPageSpecification(pageIndex), count(criteria)); } </code></pre> <p>The mapper that I use is just extending a bit DozerMapper:</p> <pre><code>public class DozerMapper{ private final org.dozer.Mapper mapper; public DozerMapper(final org.dozer.Mapper mapper) { this.mapper = mapper; } @Override public &lt;T&gt; T map(final Object source, final Class&lt;T&gt; destinationClass) { return this.mapper.map(source, destinationClass); } @Override public &lt;T&gt; List&lt;T&gt; map(final List&lt;?&gt; sources, final Class&lt;T&gt; destinationClass) { final List&lt;T&gt; result = Lists.newArrayList(); for (final Object source : sources) { result.add(map(source, destinationClass)); } return result; } </code></pre> <p>Now, all of the above works, fine is unit tested and returns the results I want. My problem is with the controller and the views....</p> <p>I have carefully read Oliver's answer to this question: <a href="https://stackoverflow.com/questions/16790371/spring-mvc-3-return-a-spring-data-page-as-json">Spring MVC 3: return a Spring-Data Page as JSON</a></p> <p>though for some reason I cannot make it work. I've added the following dependencies to my project to use HATEOAS and Spring-data-commons:</p> <pre><code>&lt;dependency&gt; &lt;groupId&gt;org.springframework.hateoas&lt;/groupId&gt; &lt;artifactId&gt;spring-hateoas&lt;/artifactId&gt; &lt;version&gt;0.7.0.RELEASE&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework.data&lt;/groupId&gt; &lt;artifactId&gt;spring-data-commons&lt;/artifactId&gt; &lt;version&gt;1.6.0.RELEASE&lt;/version&gt; &lt;/dependency&gt; </code></pre> <p>and my controller looks like this:</p> <pre><code>@Controller @SessionAttributes("person") public class PersonController @RequestMapping(value = REQUEST_MAPPING_LIST, method = RequestMethod.GET) public HttpEntity&lt;PagedResources&gt; persons(final Model model, @ModelAttribute final PersonCriteria searchCriteria, final Pageable pageable, final PagedResourcesAssembler assembler) { model.addAttribute(MODEL_ATTRIBUTE_SEARCHCRITERIA, searchCriteria); final Page&lt;PersonDTO&gt; persons = this.personService.search(searchCriteria, searchCriteria.getPageIndex()); return new ResponseEntity&lt;&gt;(assembler.toResource(persons), HttpStatus.OK); } </code></pre> <p>and my jsp:</p> <pre><code>&lt;html&gt; &lt;head&gt; &lt;title&gt;testing&lt;/title&gt; &lt;script src="jslinks for jqGrid and jquery" type="text/javascript"&gt;&lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;form:form action="person" commandName="searchCriteria" method="POST"&gt; &lt;div&gt; &lt;form:label path="code"&gt;Code: &lt;/form:label&gt; &lt;form:input path="code" type="text"/&gt; &lt;form:label path="surname"&gt;Surname: &lt;/form:label&gt; &lt;form:input path="surname" type="text"/&gt; &lt;form:label path="firstname"&gt;Firstname: &lt;/form:label&gt; &lt;form:input path="firstname" type="text"/&gt; &lt;form:label path="creationDateFrom"&gt;Creation Date From: &lt;/form:label&gt; &lt;smj:datepicker id="creationDateFrom" name="CreationDateFrom" /&gt; &lt;form:label path="creationDateTo"&gt;Creation Date To: &lt;/form:label&gt; &lt;smj:datepicker id="creationDateTo" name="CreationDateTo" /&gt; &lt;/div&gt; &lt;div&gt; &lt;input type="submit" value="search"/&gt; &lt;/div&gt; &lt;/form:form&gt; &lt;smjg:grid gridModel="gridModel" id="persons" datatype="\"json\"" url="\'person\'" jsonReader="{root:\"content\", repeatitems: false, records: \"numberOfElements\", total: \"totalPages\"}"&gt; &lt;smjg:gridColumn name="code" /&gt; &lt;smjg:gridColumn name="surname" align="left"/&gt; &lt;smjg:gridColumn name="firstname" align="left"/&gt; &lt;/smjg:grid&gt; &lt;/body&gt; &lt;/html&gt; </code></pre> <p>Explanation: the smj and smjg tags are taglibs that I'm currently working on that are linking jquery to spring mvc. Ex: smjg:grid will create the tag and the javascript that will call the jqgrid function.</p> <p>The first difference from Olivier's answer from this post <a href="https://stackoverflow.com/questions/16790371/spring-mvc-3-return-a-spring-data-page-as-json">Spring MVC 3: return a Spring-Data Page as JSON</a> is that If I infer the PersonDTO within my HttpEntity then I get the following compilation error:</p> <pre><code>Type mismatch: cannot convert from ResponseEntity&lt;PagedResources&gt; to HttpEntity&lt;PagedResources&lt;PersonDTO&gt;&gt; </code></pre> <p>the second difference is that it seems I should infer my PersonDTO into the PagedResourcesAssembler, is that correct?</p> <p>The outcome when I call the url directly localhost:8081/app/person I get a http 500 error:</p> <pre><code>org.springframework.http.converter.HttpMessageNotWritableException: Could not marshal [PagedResource { content: [Resource { content: com.app.admin.service.PersonDTO@60a349d0[id=2050,code=TEST2,firstname=ChadsdaTest,surname=Francois,creationDate=&lt;null&gt;], links: [] }, Resource { content: com.app.admin.service.PersonDTO@48462da5[id=5050,code=TESTNEW,firstname=Francois,surname=asdasdx,creationDate=&lt;null&gt;], links: [] }, Resource { content: com.app.admin.crediadmin.service.PersonDTO@5458c9fc[id=51,code=TEST,firstname=Francois,surname=asdawdsx,creationDate=&lt;null&gt;], links: [] }, Resource { content: com.app.admin.service.PersonDTO@de47c70[id=2051,code=TEST3,firstname=Chaqweqasdamsh,surname=Frasda,creationDate=&lt;null&gt;], links: [] }, Resource { content: com.app.admin.service.PersonDTO@7bd2085d[id=3053,code=TEST7,firstname=Francois,surname=Cadsdsx,creationDate=&lt;null&gt;], links: [] }, Resource { content: com.app.admin.service.PersonDTO@14676697[id=3050,code=TESTER,firstname=Francois,surname=CasdadsixChaix,creationDate=&lt;null&gt;], links: [] }, Resource { content: com.app.admin.service.PersonDTO@109de504[id=3051,code=TESTER3,firstname=FrancoisF,surname=Chtest,creationDate=&lt;null&gt;], links: [] }], metadata: Metadata { number: 0, total pages: 2, total elements: 7, size: 5 }, links: [&lt;http://localhost:8081/app/person?page=1&amp;size=5&amp;sort=surname,asc&gt;;rel="next"] }]: null; nested exception is javax.xml.bind.MarshalException - with linked exception: [com.sun.istack.SAXException2: unable to marshal type "org.springframework.hateoas.Resource" as an element because it is not known to this context.] org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter.writeToResult(Jaxb2RootElementHttpMessageConverter.java:99) org.springframework.http.converter.xml.AbstractXmlHttpMessageConverter.writeInternal(AbstractXmlHttpMessageConverter.java:66) org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:179) org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:148) org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:124) org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:69) org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:122) </code></pre> <p>and the root cause:</p> <pre><code>javax.xml.bind.MarshalException - with linked exception: [com.sun.istack.SAXException2: unable to marshal type "org.springframework.hateoas.Resource" as an element because it is not known to this context.] com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:318) com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:244) </code></pre> <p>I'm not sure of what I do wrong here. </p> <p>Althoug if I call the same url with .json then I get the json output which seems weird as I don't produce the json still.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload