Skip to content

Instantly share code, notes, and snippets.

@VineetReynolds
Last active December 14, 2015 15:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save VineetReynolds/5108580 to your computer and use it in GitHub Desktop.
Save VineetReynolds/5108580 to your computer and use it in GitHub Desktop.
Notes on attempting to solve bidirectional relationship issues with the Forge generated REST resources

@JsonManagedReference and @JsonBackReference

Requires jackson-core-asl to be added as a depenendency to the POM (so that annotations can be added to the JPA entities).

@JsonBackReference is omitted during serialization. This means that one side of the relationship will always have a property omitted during display. Also does not work with @ManyToMany bidirectional relationships, since @JsonBackReference is meant to be used on simple bean types, and not on Collection types.

Probably not recommended for use in the scaffold (even for 1:M and 1:1 bidi relationships), unless the REST resource should be managed from only one side.

@XmlTransient

This is a bit useless on it's own in AS7. It's supposed to work, but on stock Jackson+RestEasy+AS7, using it alone ends up with AS7-7059

Requires @JsonIgnore as well to be used to break out of the cycle:

@Entity
@XmlRootElement
public class Customer implements Serializable
{

   ...

   @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, orphanRemoval = true, fetch=FetchType.EAGER)
   @JsonIgnore
   @XmlTransient
   private Set<StoreOrder> orders = new HashSet<StoreOrder>();

   ...

}


@Entity
public class StoreOrder implements Serializable
{

   ...

   @ManyToOne
   private Customer customer;

   ...

}

Doesn't work in the inverse order - @JsonIgnore and @XmlTransient on StoreOrder.customer results in the customer property being absent in both Customer and StoreOrder JSON objects. It is required to be defined on the collection property.

OTOH, it works with @ManyToMany bidirectional relationships as well. Then again, it requires jackson-core-asl to be added as a dependency.

This blows up for requests that require XML responses:

For M:M bidi

[com.sun.istack.SAXException2: A cycle is detected in the object graph. This will cause infinitely deep XML: UserIdentity userName: U-A -> GroupIdentity groupName: G-A -> UserIdentity userName: U-A]

and 1:M bidi

[com.sun.istack.SAXException2: A cycle is detected in the object graph. This will cause infinitely deep XML: StoreOrder product: Apples, amount: 50 -> Customer firstName: Vineet -> StoreOrder product: Apples, amount: 50]

How very peculiar.

@XmlID and @XmlIDRef

This was attempted with Jackson 2.x (2.1.1 to be specific), since @XMLID and @XMLIDRef are JAXB annotations. The AS7 provided RestEasy-Jackson v1.9.2 provider was excluded through a jboss-deployment-structure.xml. Support for JAXB annotations in Jackson (for JSON responses) was introduced in 2.0. This also requires JPA entity classes to be annotated with @XmlAccessorType(XmlAccessType.FIELD) when the JAXB annotations are defined on the fields, failing which JAXB complains that the "Class has two properties of the same name".

Jackson fails to serialize an object graph with a bidi 1:m relation, throwing the following error:

18:06:54,457 SEVERE [org.jboss.resteasy.core.SynchronousDispatcher] (http-localhost-127.0.0.1-8080-2) Failed executing GET customers/: org.jboss.resteasy.plugins.providers.jaxb.JAXBMarshalException: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 2 counts of IllegalAnnotationExceptions
Property "id" has an XmlID annotation but its type is not String.
	this problem is related to the following location:
		at private java.lang.Long com.example.scaffoldtester.model.Customer.id
		at com.example.scaffoldtester.model.Customer
Property "id" has an XmlID annotation but its type is not String.
	this problem is related to the following location:
		at private java.lang.Long com.example.scaffoldtester.model.StoreOrder.id
		at com.example.scaffoldtester.model.StoreOrder
		at private java.util.Set com.example.scaffoldtester.model.Customer.orders
		at com.example.scaffoldtester.model.Customer

	at org.jboss.resteasy.plugins.providers.jaxb.CollectionProvider.writeTo(CollectionProvider.java:255) [resteasy-jaxb-provider-2.3.2.Final.jar:]
	at org.jboss.resteasy.core.interception.MessageBodyWriterContextImpl.proceed(MessageBodyWriterContextImpl.java:117) [resteasy-jaxrs-2.3.2.Final.jar:]
	at org.jboss.resteasy.plugins.interceptors.encoding.GZIPEncodingInterceptor.write(GZIPEncodingInterceptor.java:100) [resteasy-jaxrs-2.3.2.Final.jar:]
	at org.jboss.resteasy.core.interception.MessageBodyWriterContextImpl.proceed(MessageBodyWriterContextImpl.java:123) [resteasy-jaxrs-2.3.2.Final.jar:]
	at org.jboss.resteasy.core.ServerResponse.writeTo(ServerResponse.java:250) [resteasy-jaxrs-2.3.2.Final.jar:]
	at org.jboss.resteasy.core.SynchronousDispatcher.writeJaxrsResponse(SynchronousDispatcher.java:585) [resteasy-jaxrs-2.3.2.Final.jar:]
	at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:506) [resteasy-jaxrs-2.3.2.Final.jar:]
	at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:119) [resteasy-jaxrs-2.3.2.Final.jar:]
	at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:208) [resteasy-jaxrs-2.3.2.Final.jar:]
	at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:55) [resteasy-jaxrs-2.3.2.Final.jar:]
	at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:50) [resteasy-jaxrs-2.3.2.Final.jar:]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:847) [jboss-servlet-api_3.0_spec-1.0.0.Final.jar:1.0.0.Final]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:329) [jbossweb-7.0.13.Final.jar:]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:248) [jbossweb-7.0.13.Final.jar:]
	at org.jboss.weld.servlet.ConversationPropagationFilter.doFilter(ConversationPropagationFilter.java:62) [weld-core-1.1.5.AS71.Final.jar:2012-02-10 15:31]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:280) [jbossweb-7.0.13.Final.jar:]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:248) [jbossweb-7.0.13.Final.jar:]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:275) [jbossweb-7.0.13.Final.jar:]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:161) [jbossweb-7.0.13.Final.jar:]
	at org.jboss.as.jpa.interceptor.WebNonTxEmCloserValve.invoke(WebNonTxEmCloserValve.java:50) [jboss-as-jpa-7.1.1.Final.jar:7.1.1.Final]
	at org.jboss.as.web.security.SecurityContextAssociationValve.invoke(SecurityContextAssociationValve.java:153) [jboss-as-web-7.1.1.Final.jar:7.1.1.Final]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:155) [jbossweb-7.0.13.Final.jar:]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) [jbossweb-7.0.13.Final.jar:]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) [jbossweb-7.0.13.Final.jar:]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:368) [jbossweb-7.0.13.Final.jar:]
	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:877) [jbossweb-7.0.13.Final.jar:]
	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:671) [jbossweb-7.0.13.Final.jar:]
	at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:930) [jbossweb-7.0.13.Final.jar:]
	at java.lang.Thread.run(Thread.java:722) [rt.jar:1.7.0_10]
Caused by: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 2 counts of IllegalAnnotationExceptions
Property "id" has an XmlID annotation but its type is not String.
	this problem is related to the following location:
		at private java.lang.Long com.example.scaffoldtester.model.Customer.id
		at com.example.scaffoldtester.model.Customer
Property "id" has an XmlID annotation but its type is not String.
	this problem is related to the following location:
		at private java.lang.Long com.example.scaffoldtester.model.StoreOrder.id
		at com.example.scaffoldtester.model.StoreOrder
		at private java.util.Set com.example.scaffoldtester.model.Customer.orders
		at com.example.scaffoldtester.model.Customer

	at com.sun.xml.bind.v2.runtime.IllegalAnnotationsException$Builder.check(IllegalAnnotationsException.java:106)
	at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getTypeInfoSet(JAXBContextImpl.java:466)
	at com.sun.xml.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:298)
	at com.sun.xml.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:141)
	at com.sun.xml.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(JAXBContextImpl.java:1157)
	at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:145)
	at sun.reflect.GeneratedMethodAccessor46.invoke(Unknown Source) [:1.7.0_10]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) [rt.jar:1.7.0_10]
	at java.lang.reflect.Method.invoke(Method.java:601) [rt.jar:1.7.0_10]
	at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:211) [jboss-jaxb-api_2.2_spec-1.0.3.Final.jar:1.0.3.Final]
	at javax.xml.bind.ContextFinder.find(ContextFinder.java:392) [jboss-jaxb-api_2.2_spec-1.0.3.Final.jar:1.0.3.Final]
	at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:618) [jboss-jaxb-api_2.2_spec-1.0.3.Final.jar:1.0.3.Final]
	at org.jboss.resteasy.plugins.providers.jaxb.JAXBContextWrapper.<init>(JAXBContextWrapper.java:92) [resteasy-jaxb-provider-2.3.2.Final.jar:]
	at org.jboss.resteasy.plugins.providers.jaxb.JAXBContextWrapper.<init>(JAXBContextWrapper.java:117) [resteasy-jaxb-provider-2.3.2.Final.jar:]
	at org.jboss.resteasy.plugins.providers.jaxb.XmlJAXBContextFinder.createContextObject(XmlJAXBContextFinder.java:51) [resteasy-jaxb-provider-2.3.2.Final.jar:]
	at org.jboss.resteasy.plugins.providers.jaxb.AbstractJAXBContextFinder.createContext(AbstractJAXBContextFinder.java:129) [resteasy-jaxb-provider-2.3.2.Final.jar:]
	at org.jboss.resteasy.plugins.providers.jaxb.XmlJAXBContextFinder.findCacheContext(XmlJAXBContextFinder.java:60) [resteasy-jaxb-provider-2.3.2.Final.jar:]
	at org.jboss.resteasy.plugins.providers.jaxb.CollectionProvider.writeTo(CollectionProvider.java:219) [resteasy-jaxb-provider-2.3.2.Final.jar:]
	... 28 more

Jackson enforces the need to have IDs of type String when an attribute is annotated with @XMLID. This is due to the JAXB and XML Schema specifications; see this comment on the JAXB mailing list and the XML schema spec. EclipseLink Moxy does not enforce this strictly.

Not bothering with M:M bidi relations after this failure.

@JsonSerializer

Generic serializer implementations

Jackson (1.7+) allows for creation of modules with custom serializers and deserializers. See the jackson-hibernate-module as an example. A generic org.codehaus.jackson.map.ser.std.SerializerBase cannot be created since Jackson verifies whether an added serializer handles the Object.class type (via the SimpleModule class) and raises an exception in such an event.

TBD: Create custom serializers for every entity with relations in the model.

This is more or less where we're headed. ObjectMappers can be injected with a persistence context (atleast on AS7.1.1). Custom serializers can be created that serialize only two levels of the entity - the entity itself and related collections (and no more). During deserialization, the custom deserializers would retrieve the related entities based on the provided Ids, and recreate the object graph. The first-level entity would not be fetched from the persistence context by the custom deserializer, to ensure that changes made by clients are retained.

Custom MessageBodyReaders and MessageBodyWriters

Not sure if we need to do this, since this would override the RestEasy Jackson provider or the Jettison provider, since it would have to override the application/json media type handler.

Custom DTOs mapped to JPA entities

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment