Pages

Wednesday, August 10, 2011

Expose your Session Bean as a Web Service with JAX-WS and Eclipselink

In this blogpost I will show you how you can expose your EJB Session Bean as a Web Service, also tell you everything what I encountered and how I solved my issues. I will do this in JDeveloper and I will use the following frameworks: Eclipselink, JAX-WS and test it on WebLogic.

First let's start with the JPA part. In my project I generated some entities based on the Emp & Dept tables of the Scott schema.

In this demo I want to retrieve one department so I need to add a NamedQuery. I added Dept.findByPK to the NamedQueries annotation. Normally you only need to add the following JQL statement select o from Dept o where o.deptno = :deptid 
In this case I didn't want to set the OneToMany relation between Emp and Dept to eager. This can cause a fetch loop, so let's use the default value ( lazy ). I still need to fetch the employees on a department so I can return the department with all its employees in the Web Service response. To also fetch the employees I can add join fetch o.empList to the JQL statement.  


package nl.amis.model.entities;
import java.io.Serializable;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
@Entity
@NamedQueries({
@NamedQuery(name = "Dept.findAll",
query = "select o from Dept o"),
@NamedQuery(name = "Dept.findByPK",
query = "select o from Dept o join fetch o.empList
where o.deptno = :deptid")
})
public class Dept implements Serializable {
@Id
@Column(nullable = false)
private Long deptno;
@Column(length = 14)
private String dname;
@Column(length = 13)
private String loc;
@OneToMany(mappedBy = "dept")
private List<Emp> empList;
public Dept() {
}
public Long getDeptno() {
return deptno;
}
public void setDeptno(Long deptno) {
this.deptno = deptno;
}
public String getDname() {
return dname;
}
public void setDname(String dname) {
this.dname = dname;
}
public String getLoc() {
return loc;
}
public void setLoc(String loc) {
this.loc = loc;
}
public List<Emp> getEmpList() {
return empList;
}
public void setEmpList(List<Emp> empList) {
this.empList = empList;
}
}
view raw Dept.java hosted with ❤ by GitHub

The Emp entity also got some important adjustments.
This entity got an attribute called hiredate with  timestamp as java type. First I added the Temporal annotation with a Date value and also change the java type to Calendar else it will be ignored by the Web Service.
Because this entity has a getter to the Dept entity and Dept got one to Emp we need to break the loop for the Web Service response. You can do this by adding the XmlTransient annotation to the getDept() method. Else you will get an empty response or this error
javax.xml.stream.XMLStreamException: Premature end of file encountered

package nl.amis.model.entities;
import java.io.Serializable;
import java.util.Calendar;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.xml.bind.annotation.XmlTransient;
@Entity
@NamedQueries({
@NamedQuery(name = "Emp.findAll",
query = "select o from Emp o"),
@NamedQuery(name = "Emp.findByID",
query = "select o from Emp o where o.empno = :empno ")
})
public class Emp implements Serializable {
private Double comm;
@Id
@Column(nullable = false)
private Long empno;
@Column(length = 10)
private String ename;
@Temporal(value = TemporalType.DATE)
private Calendar hiredate;
@Column(length = 9)
private String job;
private Long mgr;
private Double sal;
@ManyToOne
@JoinColumn(name = "DEPTNO")
private Dept dept;
public Emp() {
}
@XmlTransient
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
public Double getComm() {
return comm;
}
public void setComm(Double comm) {
this.comm = comm;
}
public Long getEmpno() {
return empno;
}
public void setEmpno(Long empno) {
this.empno = empno;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public Calendar getHiredate() {
return hiredate;
}
public void setHiredate(Calendar hiredate) {
this.hiredate = hiredate;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public Long getMgr() {
return mgr;
}
public void setMgr(Long mgr) {
this.mgr = mgr;
}
public Double getSal() {
return sal;
}
public void setSal(Double sal) {
this.sal = sal;
}
}
view raw Emp.java hosted with ❤ by GitHub

This is my Session Bean where I added the WebService, WebMethod and WebParam annotation to the session bean to control the WSDL.

When you change one of these annotations you can get an deployment compiler error  like

weblogic.utils.AssertionError: ***** ASSERTION FAILED ***** Caused by: java.lang.ClassNotFoundException: nl.amis.model.services.ScottSessionBean_esxgzy_WSOImpl
at weblogic.utils.classloaders.GenericClassLoader.findLocalClass(GenericClassLoader.java:297).

To fix this you need to remove the EJBCompilerCache folder located in MiddlewareJDevPS5\jdeveloper\system11.1.1.5.37.60.13\DefaultDomain\servers\DefaultServer\cache

Also in Eclipselink I used getResultList() and return a list instead of using GetSingleResult(). With GetSingleResult you get an error when there is no result. So you need to handle that when you use it.
package nl.amis.model.services;
import java.util.List;
import javax.ejb.Stateless;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import nl.amis.model.entities.Dept;
import nl.amis.model.entities.Emp;
@WebService(serviceName = "ScottService",
portName = "ScottPort",
name = "ScottSessionBean")
@Stateless(name = "ScottSessionBean",
mappedName = "EJB-model-ScottSessionBean")
public class ScottSessionBean implements ScottSessionLocal {
@PersistenceContext(unitName="model")
private EntityManager em;
public ScottSessionBean() {
}
/** <code>select o from Dept o where o.deptno = :deptid</code> */
@WebMethod
public List<Dept> getDeptFindByPK(@WebParam(name = "deptId")
Long deptid) {
System.out.println("getDeptFindByPK");
return em.createNamedQuery("Dept.findByPK")
.setParameter("deptid", deptid).getResultList();
}
/** <code>select o from Emp o where o.empno = :empno </code> */
@WebMethod
public List<Emp> getEmpFindByID(@WebParam(name = "empNo")
Long empno) {
return em.createNamedQuery("Emp.findByID")
.setParameter("empno", empno).getResultList();
}
}
At last we can also remove the Web Service annotations from the Session Bean, create a new class and maybe do Contract first . ( this will fix the EJBcompiler errors and keeps it more clean.) In this class I inject the Session Bean to a private variable and call it's ejb methods.
package nl.amis.model.ws;
import java.util.List;
import javax.ejb.EJB;
import javax.jws.WebService;
import nl.amis.model.entities.Dept;
import nl.amis.model.entities.Emp;
import nl.amis.model.services.ScottSessionLocal;
@WebService
public class ScottService {
public ScottService() {
}
@EJB
private ScottSessionLocal scottEJB;
public Dept getDeptFindByPK(Long deptid) {
System.out.println("1");
List<Dept> result = scottEJB.getDeptFindByPK(deptid);
if ( result != null && result.size() > 0 ) {
return result.get(0);
}
return null;
}
public Emp getEmpFindByID(Long empno){
System.out.println("1");
List<Emp> result = scottEJB.getEmpFindByID(empno);
if ( result != null && result.size() > 0 ) {
return result.get(0);
}
return null;
}
}
Now you can run it and test the EJB Web Service.

1 comment: