Monday, July 5, 2010

Toplink

In Eclipse Workshop 11g, just do a New Project / JPA and follow the wizard.
It's excellent and it builds all Java classes from DB Schema.

Here some info on how to handle entities in JPA:
http://www.oracle.com/technology/products/ias/toplink/jpa/howto/create-modify-delete.html



If you want to ose other tools (not recommended)

Download:
http://www.oracle.com/technology/products/ias/toplink/index.html

Tutorial standalone:
http://www.oracle.com/technology/products/ias/toplink/doc/11110/tutorial/intro/standalone/intro_tutorial.htm



Run Workbench :
C:\apps\toplink_111130_en\utils\workbench\workbench.cmd

Unfortunately Workbench is not JPA aware.

This http://download.oracle.com/docs/cd/E13224_01/wlw/docs103/guide/ormworkbench/conGeneratingEJB3Mappings.html#creating_mappings_from_schema will explain you how to generate the JPA annotated Java classes from an existing database schema.


This is a JDeveloper demo on JPA
http://www.oracle.com/technology/products/jdev/viewlets/1013/ejb_30_viewlet_swf.html

An interesting JPA plugin for Eclipse (99 USD)
http://objectgeneration.com/eclipse/index.html

EclipseLink gives you JPA in Eclipse
http://wiki.eclipse.org/EclipseLink



Now let's setup a standalone application; I am using a schema SCOTT with username SCOTT and password TIGER on my local machine

CREATE TABLE test_entity  (
    entity_id               NUMBER(10),
    entity_name             VARCHAR2(30),
    entity_description      VARCHAR2(50)
);



The JPA entity class:

package my.entity;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Column;
import javax.persistence.Id;

/** JPA Annotation linking DB table to Java Class. */
@Entity
@Table(name = "TEST_ENTITY", schema = "SCOTT")

public class TestEntity implements Serializable {

    /** The Constant serialVersionUID. */
    private static final long serialVersionUID = 1L;

    /** The testEntity id. */
    @Id
    @Column(name = "ENTITY_ID")
    private int entityId;

    /** The testEntity name. */
    @Column(name = "ENTITY_NAME")
    private String entityName;

    /** The testEntity description. */
    @Column(name = "ENTITY_DESCRIPTION")
    private String entityDescription;

    /**
     * Instantiates a new testEntity.
     */
    public TestEntity() {}

    /**
     * Setter for the testEntity variable.
     *
     * @param entityId - the new value
     */
    public void setTestEntityId(int entityId) {
        this.entityId = entityId;
    }

    /**
     * Getter for the testEntity variable.
     *
     * @return int
     */
    public int getEntityId() {
        return entityId;
    }

    /**
     * Setter for the entityName variable.
     *
     * @param entityName - the new value
     */
    public void setEntityName(String entityName) {
        this.entityName = entityName;
    }

    /**
     * Getter for entityName variable.
     *
     * @return String
     */
    public String getEntityName() {
        return entityName;
    }

    /**
     * Setter for entityDescription variable.
     *
     * @param entityDescription - the new value
     */
    public void setEntityDescription(String entityDescription) {
        this.entityDescription = entityDescription;
    }

    /**
     * Gettter for entityDescription variable.
     *
     * @return String
     */
    public String getEntityDescription() {
        return entityDescription;
    }

}




use these jars:
commons-logging-api.jar
commons-logging.jar
odbc14.jar
toplink-essentials.jar


the persistence.xml should be:


   
    oracle.toplink.essentials.PersistenceProvider
    jdbc/localXE
    my.entity.TestEntity
    
      
      
      
      
      
      
      
      
      
      
    
  



and this is the test client:


package my.entity;

import java.io.Serializable;

import java.util.HashMap;
import java.util.Map;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


public class TestEntityHandler implements Serializable {

    /** Default Serial Version UID. */
    private static final long serialVersionUID = 1L;

    /** The Default Class LOGGER. */
    private static final Log LOGGER = LogFactory.getFactory().getInstance(
            TestEntityHandler.class.getName());

    /** The entity manager factory. */
    private static EntityManagerFactory entityManagerFactory = null;

    /** The Entity Manager for JAXB container managed persistence. */
    protected EntityManager em = null;

    /** The testEntity. */
    private TestEntity testEntity = null;

    /**
     * Public constructor.
     */
    public TestEntityHandler() { testEntity = new TestEntity(); }

    /**
     * Initialise and return the JAXB EntityManager to be used by this Class instance.
     *
     * @return - a valid [initialised] EntityManager instance
     */
    private EntityManager initialiseEntityManager() {
        try {
            if (em == null) {
                em = getEntityManager();
                LOGGER.info("Initialised Entity Manager: " + em);
            }
        } catch (Exception e) {
            LOGGER.error("Failed to initialise EntityManager instance! ... " + e, e);
        }
        return em;
    }

    /**
     * Persist a new instance of the Entity.
     */
    public void create() {
        initialiseEntityManager();
        em.getTransaction().begin();
        em.persist(testEntity);
        em.getTransaction().commit();
        //em.close();
    }

    /**
     * Persist updated details of an existing instance of the Entity.
     */
    public void update() {
        initialiseEntityManager();
        em.getTransaction().begin();
        em.merge(testEntity);
        em.getTransaction().commit();
        // This next step should not be necessary; but sometimes update is not propagated !??
        em.getTransaction().begin();
        refreshEntity(testEntity);
        em.getTransaction().commit();
        //em.close();
    }

    /**
     * Delete an existing instance of the Entity from the system.
     */
    public void delete() {
        initialiseEntityManager();
        em.getTransaction().begin();
        em.remove(testEntity);
        em.getTransaction().commit();
        //em.close();
    }


    /**
     * Gets the testEntity.
     *
     * @return the testEntity
     */
    public TestEntity getTestEntity() {
        return testEntity;
    }

    /**
     * Sets the testEntity.
     *
     * @param testEntity_ the new testEntity
     */
    public void setTestEntity(TestEntity testEntity_) {
        this.testEntity = testEntity_;
    }

    // Everything below belongs in other Class(es), reproduced here for simplicity.

    /**
     * Gets the entity manager.
     *
     * @return the entity manager
     */
    private EntityManager getEntityManager () {
        try {
            if (entityManagerFactory == null) {
                LOGGER.info("Attempting to create EntityManger ... ");
                Map override = new HashMap();
                // Override the provider used for persistence
                override.put("provider", "oracle.toplink.essentials.PersistenceProvider");
                // This name must correspond to value in META-INF/persistence.xml
                entityManagerFactory = Persistence.createEntityManagerFactory("myPersistenceUnit");
            }

            EntityManager entityManager = null;
            entityManager = entityManagerFactory.createEntityManager();
            LOGGER.info("Returning EntityManger instance:= " + entityManager);
            return entityManager;
        } catch (Exception e) {
            LOGGER.error("Exception in PersistenceManager.getEntityManager: " + e, e);
        }
        return null;
    }

    /**
     * Refresh the entity from database.
     *
     * @param TestEntity - abstract class (substituted at runtime) that is to be refreshed
     */
    protected void refreshEntity(TestEntity testEntity) {
        try {
              em.refresh(testEntity);
            } catch(EntityNotFoundException ex){
              LOGGER.debug("Failed to refresh entity; this may not be a problem");
            }
    }

    /**
     * Close the EntityManager instance.
     */
    public void closeEM() {
        em.close();
    }

    public static void main(String args[]) {
        TestEntityHandler testEntityHandler = new TestEntityHandler();
        TestEntity testEntity = new TestEntity();
        testEntity.setTestEntityId(500);
        testEntity.setEntityName("PierreLuigi");
        testEntity.setEntityDescription("This is a test");
        testEntityHandler.setTestEntity(testEntity);
        testEntityHandler.create();
        LOGGER.info("Created new TestEntity; check database now ... ");

        try {
            Thread.sleep(10000);
            LOGGER.info("Resuming ... ");
        } catch(InterruptedException e) {
            e.printStackTrace();
        }

        // Before continuing check database to ensure that object was created.

        testEntity.setEntityName("PierreLuigi Vernetto");
        testEntityHandler.setTestEntity(testEntity);
        testEntityHandler.getTestEntity().setEntityDescription("This is a second test (UPDATE)");
        testEntityHandler.update();
        LOGGER.info("Updated new TestEntity; check database now ... ");

        try {
            Thread.sleep(10000);
            LOGGER.info("Resuming ... ");
        } catch(InterruptedException e) {
            e.printStackTrace();
        }


        // Before continuing check database to ensure that object was updated.

        testEntity.setEntityName("Pierluigi Vernetto is stupid");
        testEntity.setEntityDescription("This is a second test (UPDATE)");
        testEntityHandler.delete();
        LOGGER.info("Deleted new TestEntity.");

        testEntityHandler.closeEM();

        // Now check that the object was deleted.

    }
}




Sunday, July 4, 2010

OSB: error handling, fault

First read the posts of my friend Jan and of Eric Elzinga (whom I have never had the pleasure to meet).

Here http://www.javamonamour.org/2010/04/soap-fault-in-osb.html I have taken some notes.

Here a full explanation on the topic:
http://download.oracle.com/docs/cd/E14571_01/doc.1111/e15867/modelingmessageflow.htm#i1040168


When a fault is handled in the fault handler, you have this:

$body

<env:Body xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
 <env:Fault>
  <faultcode>env:Server</faultcode>
  <faultstring/>
  <detail>
   <java:CacheStateException xmlns:java="java:com.acme.cache.commons">
    <java:CacheName>geo-data-geographic-area</java:CacheName>
    <java:Status>Not initialized</java:Status>
   </java:CacheStateException>
  </detail>
 </env:Fault>
</env:Body>


$fault

<con:fault xmlns:con="http://www.bea.com/wli/sb/context">
 <con:errorCode>BEA-380001</con:errorCode>
 <con:location>
  <con:node>RouteNodeToEJBBS</con:node>
  <con:path>response-pipeline</con:path>
 </con:location>
</con:fault>

Not necessarily "reason" and "details" are populated! Details should be retrieved from the $body variable. Reason is nowhere to be found.

(see also here http://download.oracle.com/docs/cd/E13159_01/osb/docs10gr3/userguide/context.html#wp1051816 for $fault definition)


To access individual info in the fault:
$fault/ctx:errorCode/text()

Error codes are here http://download.oracle.com/docs/cd/E13159_01/osb/docs10gr3/consolehelp/errorcodes.html

NB:
the body uses the namespace http://schemas.xmlsoap.org/soap/envelope/
here http://schemas.xmlsoap.org/soap/envelope/ you can find the XSD for the env:Fault

the fault uses the namespace http://www.bea.com/wli/sb/context



If you terminate the Error Handler with "Reply with failure", you return a HTTP 500 status. In SOAPUI, in the response panel click in RAW, you will see "HTTP/1.1 500 Internal Server Error".

The ambiguity is that if you get a fault internal to OSB - rather than from an external service - the $body doesn't contain the fault, but rather the original request message. In this case the $fault will contain the info required:

<con:fault xmlns:con="http://www.bea.com/wli/sb/context">
 <con:errorCode>BEA-382513</con:errorCode>
 <con:reason>OSB Replace action failed updating variable "body": Error parsing XML: {err}XP0006: "text '
      '": bad value for type element {http://www.acme.com/schema/GeoServicePS/v1}getLocationsByLocationIds { {http://www.w3.
org/2001/XMLSchema}anyType }</con:reason>
 <con:location>
  <con:node>RouteNodeToEJBBS</con:node>
  <con:path>request-pipeline</con:path>
 </con:location>
</con:fault>

The rule for a "universal error handler" could be:
merge whatever information comes from $body (if it's a env:Fault) and from $fault, to build a meaningful env:Fault object to return to the caller with a HTTP 500 status.


This can be used to merge env:Fault and ctx:fault into a meaningful env:Fault:

xquery version "1.0" encoding "Cp1252";
xquery version "1.0" encoding "Cp1252";
(:: pragma  parameter="$theBody" type="xs:anyType" ::)
(:: pragma  parameter="$theFault" type="xs:anyType" ::)
(:: pragma  type="xs:anyType" ::)

declare namespace xf = "http://tempuri.org/GEO_OSB_MARIA_EJBProxyProject/XQ/generateFault/";
declare namespace env = "http://schemas.xmlsoap.org/soap/envelope/";
declare namespace con = "http://www.bea.com/wli/sb/context";

declare function xf:generateFault($theBody as element(*),
    $theFault as element(*))
    as element(*) {
  
   
    {fn:concat($theBody/env:Fault/faultcode/text(), ' ', $theFault/con:errorCode/text())}
    {fn:concat($theBody/env:Fault/faultstring/text(), ' ' , $theFault/con:reason/text())}
    
    {$theBody/env:Fault/detail/*} { $theFault/con:location} 
   
  
  
};

declare variable $theBody as element(*) external;
declare variable $theFault as element(*) external;

xf:generateFault($theBody, $theFault) 



Using Axis2 as a client, during unmarshalling of the response Axis will generate a org.apache.axis2.AxisFault Java exception upon reception of a env:Fault in the body;
reason, code and details are populated.
This is optional and can be disabled.
All this takes place in Axis2 kernel, org.apache.axis2.util.Utils.getInboundFaultFromMessageContext(...)


When unittesting for SOAP Fault, assert that an AxisFault exception is generated.

SOAPUI, JUnit and functional tests

Lovely tutorials by Meera Subbarao on SOAP-UI for functional tests:

http://soa.dzone.com/articles/functional-web-services-1

http://soa.dzone.com/articles/draft-functional-web-services--0

http://soa.dzone.com/articles/functional-web-services-testin-1


More here
http://www.soapui.org/Getting-Started/functional-testing.html

and with Maven:

http://technology.amis.nl/blog/7408/automatic-testing-oracle-service-bus-using-hudson-maven-and-soapui



On running SOAPUI tests from JUnit (Hudson):

where every SOAPUI project is executed in its own JUnit test, to make it easier to identify problems:

package dk.acme.dmr.service.test.integration;

import java.io.File;
import java.io.FilenameFilter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import com.eviware.soapui.tools.SoapUITestCaseRunner;

/**
 * Run SOAPUI project files as test cases.
 * 
 * @author Acme A/S
 * @version 1.0
 * @since 1.0
 */
@RunWith(value = Parameterized.class)
public class SoapUITest {
  // The folder must be relative to this project
  private final static String SOAPUI_PROJECT_DIR = "src/test/resources/";

  private final static String ENDPOINT_VAR       = "endpoint";

  private final String        soapUiFileName;

  public SoapUITest(String soapUiFileName) {
    this.soapUiFileName = soapUiFileName;
  }

  @Parameterized.Parameters
  public static Collection<Object[]> soapUIprojectFiles() {
    File folder = new File(SOAPUI_PROJECT_DIR);
    OnlyExt onlyXml = new OnlyExt(".xml");
    String[] soapuiProjs = folder.list(onlyXml);
    List<Object[]> ret = new ArrayList<Object[]>();
    for (String s : soapuiProjs) {
      ret.add(new Object[] { s });
    }
    return ret;

  }

  @Test
  public void soapUITest() throws Exception {

    SoapUITestCaseRunner runner = new SoapUITestCaseRunner();
    runner.setProjectProperties(new String[] { new String(ENDPOINT_VAR + "=http://myserver:9001") });
    runner.setOutputFolder(System.getProperty("user.dir") + "/soapui-errors");
    runner.setProjectFile(SOAPUI_PROJECT_DIR + "/" + soapUiFileName);

    runner.run();

  }

  static class OnlyExt implements FilenameFilter {
    private final String ext;

    public OnlyExt(String _ext) {
      ext = _ext;
    }

    public boolean accept(File dir, String name) {

      return name.endsWith(ext);
    }
  }

}







I am still NOT persuaded that using SOAPUI for functional tests is better than using JUnit and Java stubs. If you go beyond the basics, SOAPUI and Groovy can be even more complex than the Java solution. I simply cannot accept writing complex code in a language that cannot be compiled and easily refactored.