1 Reply Latest reply on Jan 9, 2008 6:50 PM by mjhammel

    Returning a class with OneToMany mappings causes infinite XM

    mjhammel

      I've managed to create my first Web Services interface that retrieves a record from a MySQL db via an EJB3.0 bean generated by Hibernate using reverse engineering. However, I can't return this object to the remote client because I get the following error:

      15:22:47,324 INFO [STDOUT] Subscriber.find(): em is not null; returning subscriber for guid = CRUNCH-DEFAULT-SUPERUSER
      15:22:47,339 INFO [STDOUT] Count : 7
      15:22:47,339 INFO [STDOUT] Subscriber name : SuperUser
      15:22:47,340 INFO [STDOUT] Subscriber email: admin@localhost
      15:22:47,406 ERROR [RequestHandlerImpl] Error processing web service request
      org.jboss.ws.WSException: javax.xml.ws.WebServiceException: javax.xml.bind.MarshalException
       - with linked exception:
      [com.sun.istack.SAXException2: A cycle is detected in the object graph. This will cause infinitely deep XML: com.cei.crunch.ejb.Subscriber@1e51549 -> com.cei.crunch.ejb.Crunchroles@538af7 -> com.cei.crunch.ejb.Subscriber@1e51549]


      It seems that if your returned object includes a OneToMany mapping you get this problem. I can reference the object on the server side and return individual fields from it to the remote client, but I can't return the whole object. Since the class is being automatically generated with Hibernate, I'm wondering how I can return any db objects to callers? In fact, what if I wanted to create a new class, one that doesn't represent a table in the db, and return that? How does the client reference this class? Note that I'm using wsconsume to generate the client side classes used to interact with the Web Service. I know this latter is a WS question and not an EJB question, but my initial problem still stands - what can I do to return this EJB that has OneToMany mappings in it without getting the infinite XML error?

      This is what my Web Services/Session Bean looks like:

      package com.cei.crunch.server.ws.SubscriberServices;
      
      import javax.ejb.*;
      import javax.jws.WebService;
      import javax.jws.WebMethod;
      import javax.jws.soap.SOAPBinding;
      import javax.persistence.*;
      import javax.naming.InitialContext;
      import javax.transaction.UserTransaction;
      import javax.annotation.Resource;
      
      import com.cei.crunch.ejb.Subscriber;
      import com.cei.crunch.ejb.SubscriberHome;
      
      /* Make this an EJB3 service endpoint. */
      @Stateless
      @Remote(SubscriberServices.class)
      
      /* Make this an Web Services endpoint. */
      @WebService(
       name = "SubscriberWS",
       endpointInterface = "com.cei.crunch.server.ws.SubscriberServices.SubscriberServicesEndpoint"
       )
      @SOAPBinding(style = SOAPBinding.Style.RPC)
      
      /**
       * The .Crunch interface to Subscriber Services
       */
      public class SubscriberServices implements SubscriberServicesEndpoint {
      
       private UserTransaction utx;
       private EntityManager em;
      
       @WebMethod
       public String findSubscriber(String guid)
       {
       Subscriber s = null;
      
       InitialContext ctx = null;
       EntityManager em = null;
       try {
       ctx = new InitialContext();
       em = (EntityManager) ctx.lookup("java:/CrunchPersistence");
       utx = (UserTransaction) ctx.lookup("java:/comp/UserTransaction");
       }
       catch (Exception e)
       {
       // throw new WebServiceException(ex);
       System.out.println("Subscriber.find(): context lookup failure.");
       e.printStackTrace();
       return null;
       }
      
       if ( em == null )
       {
       System.out.println("Subscriber.find(): em is null; can't find subscriber");
       }
       else
       {
       System.out.println("Subscriber.find(): em is not null; returning subscriber for guid = " + guid);
       try {
       utx.begin();
       s = em.find(Subscriber.class, guid);
       int count = s.getCrunchroleses().size();
       if ( s != null )
       {
       System.out.println("Count : " + count);
       System.out.println("Subscriber name : " + s.getFirstname());
       System.out.println("Subscriber email: " + s.getEmailAddress());
       }
       utx.commit();
       }
       catch (Exception e)
       {
       System.out.println("Subscriber.find(): find problem.");
       e.printStackTrace();
       }
       }
       if ( s != null )
       return s.getGuid();
       else
       return null;
       }
      
      }


      Some notes on this. First, injection is not working under Web Services in 4.2.2GA (see my previous posts in the WS forum - http://www.jboss.com/index.html?module=bb&op=viewtopic&t=126927) so the context and transaction have to be created manually. Second, if you don't reference the size of one of the Set's in this EJB before referencing the fields you get LazyInitializationException errors.

      The Subscriber table looks like this - note that there are no foreign key references here, but other tables have foreign key references back to this table which causes the EJB for this table (as generated by Hibernate) to include the foreign key methods.

      create table Subscriber (
       guid varchar(50) not null,
       Username varchar(256), -- The login id for this subscriber
       password varchar(256), -- The password for this subscriber; will need encryption set for this
      
       addr1 varchar(256), -- Home address
       addr2 varchar(256), -- Home address
       city varchar(256), -- City or Town of origin
       province varchar(256), -- State or Province
      
       country varchar(256), --
       postalCode varchar(50), --
       emailAddress varchar(1024), --
       phone varchar(50), --
       latitude float, -- Used only to place subscribers on a map
       longitude float, -- Used only to place subscribers on a map
      
       firstname varchar(256), -- The subscriber first name
       lastname varchar(256), -- The subscriber last name
       organizationGuid varchar(50), -- The organization this subscriber belongs to, if any
       groupGuid varchar(50), -- The group within the organization, if any
       teamGuid varchar(50), -- The team the subscriber chose, if any
       viewLevelGuid varchar(50), -- The view level this subscriber has
       viewAccessGuid varchar(50), -- The view level this subscriber can see
       lastLogin varchar(50), -- When the subscriber last logged in
       state int, -- 0: new, 1: active, 2: inactive, 3: deleted, 4: banned
       priority int, -- Higher values have higher priority
      
       PRIMARY KEY(guid)
      )


      The EJB generated by hbm2java looks like this:

      package com.cei.crunch.ejb;
      // Generated Jan 7, 2008 3:51:39 PM by Hibernate Tools 3.2.0.CR1
      
      
      import java.util.HashSet;^M
      import java.util.Set;^M
      import javax.persistence.Column;^M
      import javax.persistence.Entity;^M
      import javax.persistence.FetchType;^M
      import javax.persistence.Id;^M
      import javax.persistence.OneToMany;^M
      import javax.persistence.Table;^M
      
      /**
       * Subscriber generated by hbm2java
       */
      @Entity
      @Table(name="subscriber"
       ,catalog="crunch"
      )
      public class Subscriber implements java.io.Serializable {
      
      
       private String guid;
       private String username;
       private String password;
       private String addr1;
       private String addr2;
       private String city;
       private String province;
       private String country;
       private String postalCode;
       private String emailAddress;
       private String phone;
       private Float latitude;
       private Float longitude;
       private String firstname;
       private String lastname;
       private String organizationGuid;
       private String groupGuid;
       private String teamGuid;
       private String viewLevelGuid;
       private String viewAccessGuid;
       private String lastLogin;
       private Integer state;
       private Integer priority;
       private Set<Subscribersession> subscribersessions = new HashSet<Subscribersession>(0);
       private Set<Crunchroles> crunchroleses = new HashSet<Crunchroles>(0);
      
      
       public Subscriber() {
       }
      
      
       public Subscriber(String guid) {
       this.guid = guid;
       }
       public Subscriber(String guid, String username, String password, String addr1, String addr2, String city, String province, String country, String postalCode, String emailAddress, String phone, Float latitude, Float longitude, String firstname, String lastname, String organizationGuid, String groupGuid, String teamGuid, String viewLevelGuid, String viewAccessGuid, String lastLogin, Integer state, Integer priority, Set<Subscribersession> subscribersessions, Set<Crunchroles> crunchroleses) {
       this.guid = guid;
       this.username = username;
       this.password = password;
       this.addr1 = addr1;
       this.addr2 = addr2;
       this.city = city;
       this.province = province;
       this.country = country;
       this.postalCode = postalCode;
       this.emailAddress = emailAddress;
       this.phone = phone;
       this.latitude = latitude;
       this.longitude = longitude;
       this.firstname = firstname;
       this.lastname = lastname;
       this.organizationGuid = organizationGuid;
       this.groupGuid = groupGuid;
       this.teamGuid = teamGuid;
       this.viewLevelGuid = viewLevelGuid;
       this.viewAccessGuid = viewAccessGuid;
       this.lastLogin = lastLogin;
       this.state = state;
       this.priority = priority;
       this.subscribersessions = subscribersessions;
       this.crunchroleses = crunchroleses;
       }
      
       @Id
      
       @Column(name="guid", nullable=false, length=50)
       public String getGuid() {
       return this.guid;
       }
      
      
       public void setGuid(String guid) {
       this.guid = guid;
       }
      
       @Column(name="Username", length=256)
       public String getUsername() {
       return this.username;
       }
      
       public void setUsername(String username) {
       this.username = username;
       }
      
       @Column(name="password", length=256)
       public String getPassword() {
       return this.password;
       }
      
       public void setPassword(String password) {
       this.password = password;
       }
      
       @Column(name="addr1", length=256)
       public String getAddr1() {
       return this.addr1;
       }
      
       public void setAddr1(String addr1) {
       this.addr1 = addr1;
       }
      
       @Column(name="addr2", length=256)
       public String getAddr2() {
       return this.addr2;
       }
      
       public void setAddr2(String addr2) {
       this.addr2 = addr2;
       }
      
       @Column(name="city", length=256)
       public String getCity() {
       return this.city;
       }
      
       public void setCity(String city) {
       this.city = city;
       }
      
       @Column(name="province", length=256)
       public String getProvince() {
       return this.province;
       }
      
       public void setProvince(String province) {
       this.province = province;
       }
      
       @Column(name="country", length=256)
       public String getCountry() {
       return this.country;
       }
      
       public void setCountry(String country) {
       this.country = country;
       }
      
       @Column(name="postalCode", length=50)
       public String getPostalCode() {
       return this.postalCode;
       }
      
       public void setPostalCode(String postalCode) {
       this.postalCode = postalCode;
       }
      
       @Column(name="emailAddress", length=1024)
       public String getEmailAddress() {
       return this.emailAddress;
       }
      
       public void setEmailAddress(String emailAddress) {
       this.emailAddress = emailAddress;
       }
      
       @Column(name="phone", length=50)
       public String getPhone() {
       return this.phone;
       }
      
       public void setPhone(String phone) {
       this.phone = phone;
       }
      
       @Column(name="latitude", precision=12, scale=0)
       public Float getLatitude() {
       return this.latitude;
       }
      
       public void setLatitude(Float latitude) {
       this.latitude = latitude;
       }
      
       @Column(name="longitude", precision=12, scale=0)
       public Float getLongitude() {
       return this.longitude;
       }
      
       public void setLongitude(Float longitude) {
       this.longitude = longitude;
       }
      
       @Column(name="firstname", length=256)
       public String getFirstname() {
       return this.firstname;
       }
      
       public void setFirstname(String firstname) {
       this.firstname = firstname;
       }
      
       @Column(name="lastname", length=256)
       public String getLastname() {
       return this.lastname;
       }
      
       public void setLastname(String lastname) {
       this.lastname = lastname;
       }
      
       @Column(name="organizationGuid", length=50)
       public String getOrganizationGuid() {
       return this.organizationGuid;
       }
      
       public void setOrganizationGuid(String organizationGuid) {
       this.organizationGuid = organizationGuid;
       }
      
       @Column(name="groupGuid", length=50)
       public String getGroupGuid() {
       return this.groupGuid;
       }
      
       public void setGroupGuid(String groupGuid) {
       this.groupGuid = groupGuid;
       }
      
       @Column(name="teamGuid", length=50)
       public String getTeamGuid() {
       return this.teamGuid;
       }
      
       public void setTeamGuid(String teamGuid) {
       this.teamGuid = teamGuid;
       }
      
       @Column(name="viewLevelGuid", length=50)
       public String getViewLevelGuid() {
       return this.viewLevelGuid;
       }
      
       public void setViewLevelGuid(String viewLevelGuid) {
       this.viewLevelGuid = viewLevelGuid;
       }
      
       @Column(name="viewAccessGuid", length=50)
       public String getViewAccessGuid() {
       return this.viewAccessGuid;
       }
      
       public void setViewAccessGuid(String viewAccessGuid) {
       this.viewAccessGuid = viewAccessGuid;
       }
      
       @Column(name="lastLogin", length=50)
       public String getLastLogin() {
       return this.lastLogin;
       }
      
       public void setLastLogin(String lastLogin) {
       this.lastLogin = lastLogin;
       }
      
       @Column(name="state")
       public Integer getState() {
       return this.state;
       }
      
       public void setState(Integer state) {
       this.state = state;
       }
      
       @Column(name="priority")
       public Integer getPriority() {
       return this.priority;
       }
      
       public void setPriority(Integer priority) {
       this.priority = priority;
       }
      @OneToMany(fetch=FetchType.LAZY, mappedBy="subscriber")
       public Set<Subscribersession> getSubscribersessions() {
       return this.subscribersessions;
       }
      
       public void setSubscribersessions(Set<Subscribersession> subscribersessions) {
       this.subscribersessions = subscribersessions;
       }
      @OneToMany(fetch=FetchType.LAZY, mappedBy="subscriber")
       public Set<Crunchroles> getCrunchroleses() {
       return this.crunchroleses;
       }
      
       public void setCrunchroleses(Set<Crunchroles> crunchroleses) {
       this.crunchroleses = crunchroleses;
       }
      }
      


        • 1. Re: Returning a class with OneToMany mappings causes infinit
          mjhammel

          Ugh. It took quite a while to figure this out, mostly because I'm just an old C developer and not a Database/Java/WebServices/SOAP/JAX/BlahBlahBlah person. But after actually considering switching to Glassfish just to solve this last problem (but deciding it wasn't necessarily any easier to understand), I sat back down and thought through what I new was the problem.

          Hibernate generates the EJB classes from my existing MySQL db. If any tables in the db have Foreign Keys defined, Hibernate will produce the set/get methods with the OneToMany annotations. These methods cannot be converted to XML (by whatever internal mechanisms are doing that) because they cause infinitely deep XML to be produced.

          The way out of this trap is to drop the use of Foreign Keys in my database. As far as I can tell, the only drawback to this is that my own code will need to check to guarantee data integrity between the old foreign key and it's associated table field - in other words, I need to make sure the other table has an entry with the specified value before adding a new entry to the table that was originally configured with the foreign key reference.

          So I pulled out the foreign keys from my test db and sure enough, I'm now able to pull a row from the db via the Web Services interface and return it to the remote client as a class object. This is exactly what my old EJB2.1/JBOSS 4.0.5GA/Middlegen based setup used to do. Now I can do it (sans foreign keys) with EJB3.0/JBOSS 4.2.2GA/Hibernate.

          Whew. That was a painful month. Here's hopin' it gets easier from here on out.