Thank you for visiting! If you liked the site, please add a bookmark, else add a critical comment! Would you like to visit Greece? Traditional Greek flag This real estate site is only available in Greek! The holy mountain

Tuesday, 23 August 2011

Providing the model layer for a ReSTful web service

In this new article we shall discuss about implementing the model layer for a simple REST style web service, presented in a book authored by Eben Hewitt, called SOA Cookbook.  In example 8-1 the author presents a plain servlet which draws  hard coded values from a pojo called ProductCatalog. The XML values returned in the browser are the ones that are statically hard coded in the pojo's constructor. The original idea to extend the example, was to provide dynamic data from querying a database.  Providing an entity bean for the Product and a stateless ejb 3 as a facade to query, seemed as a straightforward suggestion. As far as java 1.6 and JDeveloper 11.1.2 was concerned, that was merely wishful thinking!

The first exception deprived me of using directly the entity manager to access the db:

 com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 2 counts of IllegalAnnotationExceptions
javax.persistence.EntityManager is an interface, and JAXB can't handle interfaces.
    this problem is related to the following location:
        at javax.persistence.EntityManager
        at public javax.persistence.EntityManager com.soacookbook.ProductCatalog.entityManager
        at com.soacookbook.ProductCatalog
javax.persistence.EntityManager does not have a no-arg default constructor.
    this problem is related to the following location:
        at javax.persistence.EntityManager
        at public javax.persistence.EntityManager com.soacookbook.ProductCatalog.entityManager
        at com.soacookbook.ProductCatalog

The most I could do this way  was to access the db using a EJB client. The EJB code won't be displayed for brevity! Although I have received a warning that samplecode.oracle.com in its current form is being decommissioned on November 1st, 2011, the full source code can be found here, and is given as usual, without any guarantee of support, etc.

The view layer for now, is to remain as is!

The original code for the servlet from the aforementioned book, which was to remain intact, follows:

package com.soacookbook.web;

import com.soacookbook.ProductCatalog;

import java.io.IOException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;


public class SimpleRestServlet extends HttpServlet {
    private static final String CONTENT_TYPE = "text/xml; charset=UTF-8";
    private static final String DOC_TYPE = null;

    public void init(ServletConfig config) throws ServletException {
        super.init(config);
    }

    public SimpleRestServlet() {
        super();
    }

    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response) throws ServletException,
                                                              IOException {
        System.out.println("DoGet invoked on RESTful service.");
        ProductCatalog catalog = new ProductCatalog();
        Source xmlSource = asXml(catalog);
        ServletOutputStream out = response.getOutputStream();
        response.setContentType("text/xml");
        StreamResult st = new StreamResult(out);
        try {
            Transformer t = TransformerFactory.newInstance().newTransformer();
            t.transform(xmlSource, st);
        } catch (Exception e) {
            throw new ServletException(e);
        }
        System.out.println("All done.");
    }

    private static Source asXml(ProductCatalog pc) throws ServletException {
        System.out.println("Marshalling...");
        Source source = null;
        Document doc = null;
        try {
            JAXBContext ctx = JAXBContext.newInstance(ProductCatalog.class);
            Marshaller m = ctx.createMarshaller();
            DocumentBuilder parser =
                DocumentBuilderFactory.newInstance().newDocumentBuilder();
            doc = parser.newDocument();
            System.out.println("Products=" + pc);
            m.marshal(pc, doc);
            System.out.println("Marshalled catalog to XML.");
        } catch (JAXBException je) {
            throw new ServletException(je);
        } catch (ParserConfigurationException pce) {
            throw new ServletException(pce);
        }
        source = new DOMSource(doc);
        System.out.println("Returning XML source.");
        return source;
    }

    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response) throws ServletException,
                                                               IOException {
        doGet(request, response);
    }
}

The model layer with the dynamic query:

The table script as generated by JDeveloper 11.1.2: 

CREATE TABLE "FUSIONEJB3"."PRODUCT"
  (
    "ID"    VARCHAR2(255 BYTE) NOT NULL ENABLE,
    "PRICE" NUMBER(19,4),
    "NAME"  VARCHAR2(255 BYTE),
    PRIMARY KEY ("ID") USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT) TABLESPACE "USERS" ENABLE
  )
  SEGMENT CREATION IMMEDIATE PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING STORAGE
  (
    INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT
  )
  TABLESPACE "USERS" ; 


Impressive, isn't it? Next populate the table with sample data. 

REM INSERTING into PRODUCT
Insert into PRODUCT (ID,PRICE,NAME) values ('GermanBonds',50,'Euro BRD Bonds');
Insert into PRODUCT (ID,PRICE,NAME) values ('USShares ',20,'US dollar mutual fund  common shares');
 


The Product entity follows next:

package com.soacookbook.entities;

import java.io.Serializable;

import java.math.BigDecimal;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;

import javax.xml.bind.annotation.XmlType;

@XmlType
@Entity
@NamedQueries( { @NamedQuery(name = "Product.findAll",
                             query = "select o from Product o") })
public class Product implements Serializable {
    private String id;
    private String name;
    private BigDecimal price;

    public Product() {
    }

    public Product(String id, String name, BigDecimal price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    @Id
    @Column(nullable = false)
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }
}

Some more modifications of the book code:

Although the first thought was to preserve the original working source code provided by the book author, some modifications were necessary. The final form of ProductCatalog follows:

package com.soacookbook;

import com.soacookbook.client.ProductCatalogSEClient;
import com.soacookbook.entities.Product;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class ProductCatalog {
   
   
    private List<Product> products;
   
    public ProductCatalog() {
        super();
        ProductCatalogSEClient productCatalogSEClient = new ProductCatalogSEClient();
         products = productCatalogSEClient.getAllProducts();
              
    }

    //Don't you dare, no other getter there!   

    public void setProducts(List<Product> products) {
        this.products = products;
    }

  
}

Had you dared to generate an accessor, this would be the exception:


com.sun.xml.bind.v2.runtime.IllegalAnnotationsException:
 1 counts of IllegalAnnotationExceptions
Class has two properties of the same name "products"
this problem is related to the following location:at
public java.util.List com.soacookbook.ProductCatalog.getProducts()
at com.soacookbook.ProductCatalog
this problem is related to the following location:at
 public java.util.List com.soacookbook.ProductCatalog.products
at com.soacookbook.ProductCatalog



Application-managed EntityManagers, outside the Java EE container, save the day!

The first successful attempt to access the db was via the Java SE client:

package com.soacookbook.client;

import com.soacookbook.ProductCatalog;
import com.soacookbook.entities.Product;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;

public class ProductCatalogSEClient {
   
    private  EntityManager entityManager;
    private  EntityManagerFactory entityManagerFactory;
   
    private  Hashtable emProps = new Hashtable();
   
   
    public ProductCatalogSEClient() {
        super();
    }
   
    public static void main(String[] args) {

        ProductCatalogSEClient productCatalogSEClient = new ProductCatalogSEClient();
        List<Product> products = productCatalogSEClient.getAllProducts();
        Iterator  i = products.iterator();
                  while (i.hasNext())
                  {
                    Product product = (Product) i.next();
                    System.out.println("Id:"+ product.getId() +" Name:"+product.getName()
                                             + " price " + product.getPrice());
                  }
                 
    }
   
    public  List<Product> getAllProducts(){
          
           List<Product> products= null;
           try {
             entityManagerFactory =
                Persistence.createEntityManagerFactory("JavaSEClient");
                // not the same with example8-1  !
            entityManager = entityManagerFactory.createEntityManager(emProps);
            Query query = entityManager.createNamedQuery("Product.findAll");
            products=query.getResultList();
          
        } catch (Exception e) {
            // TODO: Add catch code
            e.printStackTrace();
        } finally {
            entityManager.close();
            entityManagerFactory.close();
          
        }
            return products;
        }

}

A second introduction to EE is superfluous: no more user credentials please!

However, if one copied the working properties for the SE client and pasted to the EE edition unit:

Internal Exception: java.sql.SQLException: User: fusionEJB3, failed to be authenticated.
Error Code: 0
    at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:309)
    at org.eclipse.persistence.sessions.JNDIConnector.connect(JNDIConnector.java:138)
    at org.eclipse.persistence.sessions.JNDIConnector.connect(JNDIConnector.java:94)
    at org.eclipse.persistence.sessions.DatasourceLogin.connectToDatasource(DatasourceLogin.java:162)


The correct persistence.xml has no user credentials for the EJB client, unlike the java SE one:

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="1.0">
    <persistence-unit name="example8-1">
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        <jta-data-source>jdbc/fusionEJB3</jta-data-source>
        <class>com.soacookbook.entities.Product</class>
        <properties>
            <property name="eclipselink.target-server" value="WebLogic_10"/>
            <property name="eclipselink.target-database" value="Oracle"/>
            <property name="eclipselink.logging.level" value="ALL"/>
          </properties>
    </persistence-unit>
    <persistence-unit name="JavaSEClient" transaction-type="RESOURCE_LOCAL">
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        <class>com.soacookbook.entities.Product</class>
        <properties>
            <property name="eclipselink.target-server" value="WebLogic_10"/>
            <property name="eclipselink.target-database" value="Oracle"/>
            <property name="javax.persistence.jdbc.driver"                value="oracle.jdbc.OracleDriver"/>
            <property name="javax.persistence.jdbc.password"          value="fusionEJB3"/>
            <property name="javax.persistence.jdbc.user" value="fusionEJB3"/>
            <property name="eclipselink.logging.level" value="ALL"/>
            <property name="eclipselink.orm.validate.schema" value="true"/>
            <property name="javax.persistence.jdbc.url"     value="jdbc:oracle:thin:@hera:1521:orcl"/>
        </properties>
    </persistence-unit>
</persistence>


Finally, if one types: http://localhost:7101/example8-1/Products

the XML datagram appears:

<productCatalog>
<products><id>GermanBonds</id><name>Euro BRD Bonds</name><price>50</price></products>
<products><id>USShares </id><name>US dollar mutual fund  common shares</name><price>20</price></products>
</productCatalog>

Making use of EJB 3.1 features via Netbeans 7.0.1

If one has the option of using the latest jdk 1.7, things can be far less complicated:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.soacookbook.facade;

import com.soacookbook.entities.Product;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.ejb.Stateless;
import javax.ejb.LocalBean;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.xml.bind.annotation.XmlRootElement;

/**
 *
 * @author Nick
 */
@XmlRootElement
@Stateless
@LocalBean

public class ProductCatalog implements Serializable {
     private static final long serialVersionUID = 1L;
     @PersistenceContext(unitName="chapter8-1-ejbPU")
       private EntityManager em;
    private List<Product> products;

    public List<Product> getProducts() {
        return products;
    }

    public void setProducts(List<Product> products) {
        this.products = products;
    }
  
  
    public List<Product> getAllProducts() {
       
        Query  query = em.createNamedQuery("Product.findAll");
        products = new ArrayList<Product>();
        //logger.info("Query max results: "+ query.getMaxResults());
        products = query.getResultList();
        return products;
    }
}

As far as the servlet is concerned, one may now use dependency injection:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.soacookbook.web;

import com.soacookbook.facade.ProductCatalog;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.EJB;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;

/**
 * @date Aug 26, 2011
 * @author Nick
 */
public class SimpleRestServlet   extends HttpServlet {
  
@EJB
private ProductCatalog catalog;

    public void init(ServletConfig config) throws ServletException {
        //try {
            super.init(config);
             /*Context context = new InitialContext();
                 catalog =
                            (ProductCatalog) context.lookup("java:global/chapter8-1/chapter8-1-ejb/ProductCatalog");*/
                catalog.getAllProducts();
       /* } catch (NamingException ex) {
            Logger.getLogger(SimpleRestServlet.class.getName()).log(Level.SEVERE, null, ex);
        }*/
    }

    public SimpleRestServlet() {
        super();
    }

    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response) throws ServletException,
                                                              IOException {
        try {
            System.out.println("DoGet invoked on RESTful service.");
          
            Source xmlSource = asXml(catalog);
            ServletOutputStream out = response.getOutputStream();
            response.setContentType("text/xml");
            StreamResult st = new StreamResult(out);
            try {
                javax.xml.transform.Transformer t = TransformerFactory.newInstance().newTransformer();
                t.transform(xmlSource, st);
            } catch (Exception e) {
                throw new ServletException(e);
            }
            System.out.println("All done.");
        } catch (Exception ex) {
            Logger.getLogger(SimpleRestServlet.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private static Source asXml(ProductCatalog pc) throws ServletException {
        System.out.println("Marshalling...");
        Source source = null;
        Document doc = null;
        try {
            JAXBContext ctx = JAXBContext.newInstance(ProductCatalog.class);
            Marshaller m = ctx.createMarshaller();
            DocumentBuilder parser =
                DocumentBuilderFactory.newInstance().newDocumentBuilder();
            doc = parser.newDocument();
            System.out.println("Products=" + pc);
            m.marshal(pc, doc);
            System.out.println("Marshalled catalog to XML.");
        } catch (JAXBException je) {
            throw new ServletException(je);
        } catch (ParserConfigurationException pce) {
            throw new ServletException(pce);
        }
        source = new DOMSource(doc);
        System.out.println("Returning XML source.");
        return source;
    }

    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response) throws ServletException,
                                                               IOException {
        doGet(request, response);
    }

}


The morale of the story
Automatically generated code is not certain to be error free! It could cause run time exceptions in one IDE and specific application servers, ie Weblogic 10.3.5. In addition, migration to newer  jdk versions are advantageous in most cases.
Even though some resources, excellent books, or tutorials such as those from the open source or Sun Netbeans community, are highly advanced, demonstrating  the latest technology quite well, some are still incomplete. Given that oracle bred java EE developers have a different philosophy, i.e. are accustomed to setup db access as a first step, it is obvious that there is a huge gap in the way of thinking, between the two communities. This gap is for Sun Oracle press, university, marketing, or whatever  other department to bridge. Until then, all incomplete open source recipes are candidates for some kind of integration coding exercise, or maybe not?

No comments:

Post a Comment