Thursday, December 22, 2011

Fix Jackson/JAXB JSON Serialization Problem with Spring Data Neo4j @NodeEntity Objects

Spring Data Neo4j @NodeEntity-annotated objects are proxy-enriched by Spring Data Neo4j aspects using AspectJ. There are times we want to expose these objects via JAX-RS REST API or JAX-WS / SOAP web services and then (by default) we will have problems.

The error stacktrace messages (here using JBoss AS 7.0.2) are along the lines of:

HTTP Status 500 - org.codehaus.jackson.map.JsonMappingException: No serializer found for class org.neo4j.graphdb.DynamicRelationshipType and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: com.satukancinta.domain.User["enjoyActivities"]->org.springframework.data.neo4j.fieldaccess.ManagedFieldAccessorSet[0]->com.satukancinta.domain.Activity["persistentState"]->org.neo4j.rest.graphdb.entity.RestNode["relationships"]->org.neo4j.rest.graphdb.entity.RestRelationship["type"])


type Status report

message org.codehaus.jackson.map.JsonMappingException: No serializer found for class org.neo4j.graphdb.DynamicRelationshipType and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: com.satukancinta.domain.User["enjoyActivities"]->org.springframework.data.neo4j.fieldaccess.ManagedFieldAccessorSet[0]->com.satukancinta.domain.Activity["persistentState"]->org.neo4j.rest.graphdb.entity.RestNode["relationships"]->org.neo4j.rest.graphdb.entity.RestRelationship["type"])

description The server encountered an internal error (org.codehaus.jackson.map.JsonMappingException: No serializer found for class org.neo4j.graphdb.DynamicRelationshipType and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: com.satukancinta.domain.User["enjoyActivities"]->org.springframework.data.neo4j.fieldaccess.ManagedFieldAccessorSet[0]->com.satukancinta.domain.Activity["persistentState"]->org.neo4j.rest.graphdb.entity.RestNode["relationships"]->org.neo4j.rest.graphdb.entity.RestRelationship["type"])) that prevented it from fulfilling this request.

To solve this problem, annotate your entity class with @JsonAutoDetect(JsonMethod.NONE) :

import org.codehaus.jackson.annotate.JsonAutoDetect;

import org.codehaus.jackson.annotate.JsonMethod;

import org.codehaus.jackson.annotate.JsonProperty;


@NodeEntity @JsonAutoDetect(JsonMethod.NONE)

public class User implements NodeBacked {

then manually annotate each property you want to serialize with @JsonProperty :

@JsonProperty

public String getName() {

return name;

}

Now the JAX-RS Application works as intended and everybody is happy. :-) There's also a StackOverflow thread that discusses this problem.

To learn more about Java Web Development using Java EE 6, I highly recommend The Java EE 6 Tutorial: Basic Concepts (4th Edition) (Java Series) by Eric Jendrock, Ian Evans, Devika Gollapudi and Kim Haase.

How to Resolve Spring Data Neo4j / Jersey / Jackson Conflict with JBoss AS 7 RESTEasy

I'm using Spring Data Neo4j REST Client which uses Jersey JAX-RS Client. Unfortunately when deployed to JBoss AS 7.0.2, it conflicts with the built-in RESTEasy deployer (bug DATAGRAPH-159).

In order to run the application, jersey-server must be excluded :

<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-neo4j-rest</artifactId> <version>${spring-data-neo4j.version}</version> <exclusions> ... <exclusion> <artifactId>jersey-server</artifactId> <groupId>com.sun.jersey</groupId> </exclusion> </exclusions> </dependency>

Another issue I came across is my web application is also a JAX-RS Service Application, therefore it requires JBoss RESTEasy. Unfortunately Jackson JSON Provider which is a dependency of Neo4j REST Client and Jersey JAX-RS Client conflicts with RESTEasy's Jackson Provider, with the following exception stacktrace message:

java.lang.RuntimeException: Unable to instantiate MessageBodyReader  org.jboss.resteasy.spi.ResteasyProviderFactory.registerProvider(ResteasyProviderFactory.java:760)  org.jboss.resteasy.spi.ResteasyProviderFactory.registerProvider(ResteasyProviderFactory.java:742)  org.jboss.resteasy.spi.ResteasyDeployment.registerProvider(ResteasyDeployment.java:505)  org.jboss.resteasy.spi.ResteasyDeployment.registration(ResteasyDeployment.java:305)  org.jboss.resteasy.spi.ResteasyDeployment.start(ResteasyDeployment.java:225)  org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.init(ServletContainerDispatcher.java:67)  org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.init(HttpServletDispatcher.java:36)  org.jboss.as.web.security.SecurityContextAssociationValve.invoke(SecurityContextAssociationValve.java:139)  org.jboss.as.web.NamingValve.invoke(NamingValve.java:57)  org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)  org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:362)  org.apache.coyote.http11.Http11AprProcessor.process(Http11AprProcessor.java:897)  org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:626)  org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:2054)  java.lang.Thread.run(Thread.java:722)

root cause

java.lang.RuntimeException: Illegal to inject a message body into a singleton into public org.codehaus.jackson.jaxrs.JacksonJsonProvider(org.codehaus.jackson.map.ObjectMapper,org.codehaus.jackson.jaxrs.Annotations[])  org.jboss.resteasy.core.MessageBodyParameterInjector.inject(MessageBodyParameterInjector.java:209)  org.jboss.resteasy.core.ConstructorInjectorImpl.injectableArguments(ConstructorInjectorImpl.java:63)  org.jboss.resteasy.core.ConstructorInjectorImpl.construct(ConstructorInjectorImpl.java:129)  org.jboss.resteasy.spi.ResteasyProviderFactory.getProviderInstance(ResteasyProviderFactory.java:1038)

I thought this was issue RESTEASY-503, because JBoss AS 7.0.2 happened to use the somewhat buggy RESTEasy 2.2.1.GA. But it turns out there is an easy fix to this problem, thanks to Configuring Module Classloading in JBoss AS 7.

Edit src/main/webapp/WEB-INF/jboss-deployment-structure.xml as follows:


<jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.0">

<deployment>

<dependencies>

<module name="org.codehaus.jackson.jackson-jaxrs"/>

<module name="org.codehaus.jackson.jackson-core-asl"/>

<module name="org.codehaus.jackson.jackson-mapper-asl"/>

</dependencies>

</deployment>

</jboss-deployment-structure>

Now Spring Data Neo4j, Jersey Client with JSON Jackson Provider, and my JAX-RS Application served by RESTEasy, all can coexist in the same web application WAR. :-)

To learn more about Java Web Development using Java EE 6, I highly recommend The Java EE 6 Tutorial: Basic Concepts (4th Edition) (Java Series) by Eric Jendrock, Ian Evans, Devika Gollapudi and Kim Haase.

Wednesday, December 7, 2011

I don't "get" JSON Output in Pentaho Data Integration (PDI) / Kettle

I don't understand how to use the JSON Output step properly in Kettle aka Pentaho Data Integration (PDI).

With "Nr of rows in a bloc" set to 0 or 3, I got:

{
  "categories": [
    {
      "code": "WORKAHOLIC-CHIC"
    },
    {
      "name": "Workaholic Chic"
    },
    {
      "description": "Move ! Move ! move...!!\nLight Up Your Day... with a perfect match, \nPadanan busana kerja Professsional look, Powerfull & Fashionable,\nwhich got several design for different mood,  multifunction,\nMemorable style!\nLet’s be and stay Tuneeca...\n"
    }
  ]
}

which is basically only the last record.

With "Nr of rows in a bloc" set to 1, I got:

{
  "categories": [
    {
      "code": "AKSESORI-LIGHT-UP-YOUR-DAY"
    },
    {
      "name": "Aksesori Light Up Your Day"
    },
    {
      "description": "-"
    },
    {
      "code": "AKSESORIS-APRIL-2009"
    },
...

What I'm trying to get is:

{ "categories": [
  { "code": "AKSESORI-LIGHT-UP-YOUR-DAY",
     "name": "Aksesori Light Up Your Day"
     "description": "Very cool" },
  { "code": .........

Contrast this with the XML Output, which I get the following correct output right from first try:

<?xml version="1.0" encoding="UTF-8"?>
<categories>
  <category>
    <code>AKSESORI-LIGHT-UP-YOUR-DAY</code>
    <name>Aksesori Light Up Your Day</name>
    <description>-</description>
  </category>
  <category>
    <code>AKSESORIS-APRIL-2009</code>
...

An additional plus is that XML Output already performs a bit of output pretty formatting, which I appreciate very much. (JSON Output outputs everything in a single line)

Those two Output steps gets the same input data.

Any ideas ?