Combining ORM and SOAP - Part 1

By Scott Balmos
Posted by Jack Vaughan
In today’s incarnation of the Interop blog, I’ll follow Ted Neward’s lead and give an example of how to combine SOAP with another one of the most widely-used classes of application frameworks - Object/Relation Mapping.

Aside from Web Services, one of the most popular classes of application frameworks deals with object persistence. We all love writing raw SQL just as much as we love writing our own WSDL files, right? So, I’ll give a simple example here with two entity classes and an appropriate SOAP endpoint to use them. Just as much as we love “Hello World” type programs, we’ll show a standard User / Group type setup here, something that easily shows the object relations.

I’ll be writing in JPA for EJB3. If anyone is interested in equivalent C# code, let me know in a comment and I’ll post it up.

First, the entities…

package test;

@Entity
public class User implements Serializable {
    private Long id;
    private String userName;
    private String password;
    private String firstName;
    private String lastName;

    @Id
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    public String getUserName() { return userName; }{
    public void setUserName(String userName) { this.userName = userName;{
}{
{
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password;
}

    public String getFirstName() { return firstName; }
    public void setFirstName(String firstName) { this.firstName =
firstName; }

    public String getLastName() { return lastName; }
    public void setLastName(String lastName) { this.lastName = lastName;
} }

package test;

@Entity
public class Group implements Serializable {
    private Long id;
    private String name;
    private List members = new ArrayList();

    @Id
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    @OneToMany
    public List getMembers() { return members; }
    public void setMembers(List members) { this.members = members;
} }

And now, the SOAP endpoint…

package test;

@WebService
public class TestService
{
    @PersistenceContext private EntityManager em;

    public User getUserById(Long userId)
    {
        User user = em.find(User.class, userID);
        return user;
    }

    public User addUser(String firstName, String lastName, String
userName, String password)
    {
        User user = new User();
        user.setFirstName(firstName);
        user.setLastName(lastName);
        user.setUserName(userName);
        user.setPassword(password);
        em.persist(user);
        return user;
    }

    public Group getGroupById(Long groupId)
    {
        Group group = em.find(Group.class, groupId);
        return group;
    }
}
There are obviously quite a number of other methods that could be created. But these show the general idea. A user entity class has a username, password, first name, and last name. A group entity is basically a collection of users, hence the OneToMany collection mapping annotation. In our endpoint, we use a standard JPA EntityManager to handle everything.

One thing to note is the addUser method, and modification methods in general when combining SOAP and ORM. You’re probably wondering why I didn’t create addUser(User user)? Most times, stub generators will put service stubs and custom type classes with all of the XML annotations in a subpackage, like something.ws. In this case, the entity classes on the client side would be in package test.ws. So if our client tried to pass a User object to addUser(), it would really get a test.ws.User instead of test.User. The test.ws.User class doesn’t have any of the required JPA entity annotations, so the EntityManager would scream at us.

So I tend to have modification methods that pass as parameters either base types, or the entity IDs of entities when creation relations. If I had expanded the SOAP endpoint example, I would’ve added addMemberToGroup(Long groupId, Long userId). Another good thing with this route is that you ensure your client is using a proper entity ID and is only changing the entity field(s) that should be modified. If I had modifyUserPassword(User user, String password), then we certainly know that we should be modifying a password. But the passed in User entity might’ve also had the username, first name, or last name modified without our authorization (or knowledge). In this route, we can do proper data checking before committing the new field values to the entity.

Next time, I’ll expand on the above samples to show two issues that have to be dealt with when using SOAP with Maps, and especially with lazy fetching (a core tenant of object relational persistence).

4 Responses to “Combining ORM and SOAP - Part 1”

  1. Michael Says:

    Thanks for the example, Scott. I would like to see how this is done with C#.

  2. Scott Balmos Says:

    As requested, here is the equivalent example in ASP.Net / C#. Since ASP.Net doesn’t really have a standard framework for interchangeable persistence architectures, I used NHibernate along with the NHibernate Mapping Attributes contributed assembly. This allows for mapping metadata to be in the class attributes rather than in the old-style Hibernate HBM XML files.

    First, create a new ASP.Net Web Service. Add assembly references to NHibernate.dll and NHibernate.Mapping.Attributes.dll . Add the following lines to Web.config (taken almost verbatim from NHibernate’s documentation):

    NHibernate.Connection.DriverConnectionProvider
    Server=(local);initial catalog=TSSBlog;Integrated Security=SSPI

    In this example, I’m using SQL Server 2005 Express with a database named TSSBlog. Further NHibernate configuration information is outside the scope of this entry.

    Now, the entities…

    using System;
    using System.Collections.Generic;
    using NHibernate.Mapping.Attributes;

    namespace test
    {
    [Serializable, Class]
    public class User
    {
    private long id;
    private string userName;
    private string password;
    private string firstName;
    private string lastName;

    [Id]
    public long Id
    {
    get { return id; }
    set { id = value; }
    }

    [Property]
    public string UserName
    {
    get { return userName; }
    set { userName = value; }
    }

    [Property]
    public string Password
    {
    get { return password; }
    set { password = value; }
    }

    [Property]
    public string FirstName
    {
    get { return firstName; }
    set { firstName = value; }
    }

    [Property]
    public string LastName
    {
    get { return lastName; }
    set { lastName = value; }
    }
    }

    [Serializable, Class]
    public class Group
    {
    private long id;
    private string name;
    private List members = new List();

    [Id]
    public long Id
    {
    get { return id; }
    set { id = value; }
    }

    [Property]
    public string Name
    {
    get { return name; }
    set { name = value; }
    }

    [OneToMany]
    public List Members
    {
    get { return members; }
    set { members = value; }
    }
    }
    }

    Nothing really Earth-shattering. Almost exactly like the Java entities. Now the service endpoint (in the code-behind C# file named Service.cs):

    using System;
    using System.IO;
    using System.Reflection;
    using System.Web.Services;
    using NHibernate;
    using NHibernate.Cfg;
    using NHibernate.Mapping.Attributes;

    namespace test
    {
    [WebService(Namespace = “http://tempuri.org/”)]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class TestService : System.Web.Services.WebService
    {
    private Configuration cfg;
    private ISession em;

    public TestService()
    {
    cfg = new Configuration();
    MemoryStream stream = new MemoryStream();
    HbmSerializer.Default.Validate = true;
    HbmSerializer.Default.Serialize(stream, Assembly.GetExecutingAssembly());
    stream.Position = 0;
    cfg.Configure();
    cfg.AddInputStream(stream);
    stream.Close();
    em = cfg.BuildSessionFactory().OpenSession();
    }

    [WebMethod]
    public User GetUserById(long userId)
    {
    User user = em.Get(typeof(User), userId) as User;
    return user;
    }

    [WebMethod]
    public User AddUser(string firstName, string lastName, string userName, string password)
    {
    User user = new User();
    user.FirstName = firstName;
    user.LastName = lastName;
    user.UserName = userName;
    user.Password = password;

    em.SaveOrUpdate(user);
    return user;
    }

    [WebMethod]
    public Group GetGroupById(long groupId)
    {
    Group group = em.Get(typeof(Group), groupId) as Group;
    return group;
    }
    }
    }

    I’ll get back to the NHibernate cruft in the constructor in a moment. Finally, Service.asmx (mainly because I renamed the endpoint class):

    If you compare this code to the original Java code, all the explanation is the same. The only difference here is in the constructor, and this is also almost verbatim taken from NHibernate documentation. The MemoryStream is used by the internal NHibernate Mapping Attributes HBM generator. It auto-generates HBM XML mapping files on the fly and writes it to the stream, which is then fed into the NHibernate Configuration object. We finally pull open a session, which for our intents here is the same as a JPA EntityManager. This is why I kept the original variable name of “em”, just to keep things consistent with the original code.

    Hope this is a useful-enough conversion. I don’t do all that much ASP.Net coding. :)

  3. Anand Says:

    Thanks Scott for the sample code.
    About Java soap example, can the J2EE containers generate the stubs & custom type classes in a configurable package name ,instead of the default say test.ws package in your example. Is there any way for the client code to use User,Group classes
    to talk to the example web servce? Thanks

  4. Steve Says:

    Error 39 TestCase ‘Bistro.Domain.CompaniesTests.General’
    failed: System.Web.Services.Protocols.SoapException : System.Web.Services.Protocols.SoapException: Server was unable to process request. —> System.InvalidOperationException: There was an error generating the XML document. —> System.InvalidOperationException: CProxyTypeMyObject_NHibernate_ProxyINHibernateProxy_System_Runtime_SerializationISerializable2 cannot be serialized because it does not have a parameterless constructor.

    This is error if you return it with lazyload on…big problem with xml serialization and nhibernate


Leave a Reply