Simple Xml-to-Jpa data service with Smooks, Play2 for java and Ebeans JPA engine
When experimenting how to develop simple end-to-end xml-to-jpa service
- SOAP UI can be used to emulate client application.
- Smooks Xml-to-java example can be used to define xml payload, transform rules and java domain classes.
- Play2 template application can be customized to embed xml-to-java example with modifications for JPA persistence.
Why to use xml-transformations i.e. Smooks?
Why to use Ebean JPA engine?
Why not to use Jaxb?
XML services concept
Any client can send using http post xml messages containing "order" elements to play2 controller which is connected to "/order" url.
On server side "order" elements are transformed to domain classes. Transformation rules are stored on smooks-config xml file. Ebean and H2 are persisting information.
Setting up environment
- Soap UI as Client http://sourceforge.net/projects/soapui/files/
- Play2 for java as Server http://www.playframework.com/download
- Netty is HTTP based non blocking app server http://netty.io/
- Ebean is JPA compatible object relational mapping engine http://www.avaje.org/
Creating and testing sample Play2 app
- Go to root of your development directory
- give command "play new xml-to-jpa"
Start play shell
- go to project directory xml-to-jpa
- give command "play"
- start app with "~run"
- go to "localhost:9000"
More details of template play2 application at http://www.playframework.com/documentation/2.1.x/Home
Setting up IDE
Generate IDE configuration
- start shell "play"
- generate configuration "eclipse with-source=true"
- Open Eclipse
- Let Eclipse to create new workspace
- Export / General / Java Project from existing sources
Enabling H2 and Ebean
- Open conf/application.conf
- uncomment lines under "database configuration" to enable H2 support
- uncomment lines under "ebean configuration" to enable JPA engine
- change "ebean.default" to point "example.models.*" as on Smooks example
Extending app with JPA model
- Open Order class source on github
- https://github.com/smooks/smooks/blob/v1.5.1/smooks-examples/xml-to-java/src/main/java/example/model/Order.java
- Use example.model as package for classes
- Create classes Header, CustomerOrder and OrderItem
- note: we must use CustomerOrder instead of Order as Order is reserved word in SQL
- Add play.db.ebean.Model as superclass
- Define all classes as entities with @Entity annotation
- add id fields and @Id annotations
- add other needed fields from https://github.com/smooks/smooks/tree/v1.5.1/smooks-examples/xml-to-java/src/main/java/example/model
- Add back reference to OrderItem
- Annotate classes with @ManyToOne, @OneToOne and @OneToMany JPA annotations
- Add static Finder as find variable to CustomerOrder
- Note: Usage of Finder isn't mandatory, but it makes complex query operations single liners.
@Entitypublic class CustomerOrder extends Model {
@Id
public Long id;
/**
* Cascades saves, which means dependent object will be saved as main object is saved.
*/
@OneToOne (cascade = CascadeType.ALL, fetch=FetchType.EAGER)
public Header header;
/** Saves on collections are cascaded.
* Fetches are eager, which might result some extra access to db.
*/
@OneToMany(cascade = CascadeType.ALL, mappedBy = "customerOrder", fetch=FetchType.EAGER)
public List<OrderItem> orderItems = new ArrayList<OrderItem> ();
/**
* find static member variable to help searches
*/
public static Finder<Long, CustomerOrder> find = new Finder<Long, CustomerOrder>(
Long.class, CustomerOrder.class);
}
It’s important to note that if you want to have real JPA it can be plugged in, but it’s there not as per default http://www.playframework.com/documentation/2.1.1/JavaJPA
Document domain model
Bootstrap database creation
CustomerOrder database ddl should look like
Add Smooks dependencies to SBT build
Prepare tranformation rules
Note: HTML report drains memory. HTML report is vital tool for checking your mappings / selectors, but should be used only with limited amount of data. Use it only for development and never with xml streams of megabytes.
Note: Instance of Smooks is thread safe and should be static as it's initialization per request basis is expensive. Here is is kept inside method scope to minimize changes.
Define entry point
http://nikkijuk.blogspot.de/2013/08/simple-java-xml-web-services.html
Define view template
Define controller method
See list of orders
Define test data
Load test data
If you feel more academic interest then Yaml specification might help http://www.yaml.org/spec/1.2/spec.html
Setup SOAP ui
Review your entites
- Create class diagram with ObjectAid http://www.objectaid.com/class-diagram
- Drag classes to canvas of diagram
Create database instance
- make sure application is compiled and running at port 9000
- go to localhost:9000
- Wait that play2 asks “Database 'default' needs evolution!”
- Check generated database ddl and accept it
CustomerOrder database ddl should look like
create table customer_order (You can access H2 database out of process to see empty database http://www.h2database.com/html/quickstart.html
id bigint not null,
header_id bigint,
constraint pk_customer_order primary key (id));
Extending app with transformations
- Open file project/Build.scala
- Add libs to appDependencies
- "org.milyn" % "milyn-smooks-core" % "1.5.1",
- "org.milyn" % "milyn-smooks-javabean" % "1.5.1",
- add repository to settings method of play project
- resolvers += "JBoss repository" at "http://repository.jboss.org/nexus/content/groups/public-jboss/"
- update dependencies by compiling and starting project “play clean eclipse ~run“
- refresh project in eclipse with File / Refresh
Prepare tranformation rules
- get smooks-config.xml from git to conf directory https://github.com/smooks/smooks/blob/v1.5.1/smooks-examples/xml-to-java/smooks-config.xml
- Change class name "example.model.Order" to "example.model.CustomerOrder" at definition of “order” bean
- Add to “orderItem” bean property “customerOrder” which references to “order” bean <jb:wiring property="customerOrder" beanIdRef="order" />
- See example at https://github.com/smooks/smooks/blob/v1.5.1/smooks-examples/xml-to-java/src/main/java/example/Main.java
- Get static runSmooks () method and copy it to app.controllers.Application
- Add input parameter of type org.w3c.dom.document and replace input stream with it using http://docs.oracle.com/javase/7/docs/api/javax/xml/transform/dom/DOMSource.html
- Change html report creation to point logs directory
- Result of refactoring can be seen here https://github.com/nikkijuk/smooks-play2-xml-to-jpa/blob/master/smooks-play2-xml-to-jpa/app/controllers/Application.java
Note: HTML report drains memory. HTML report is vital tool for checking your mappings / selectors, but should be used only with limited amount of data. Use it only for development and never with xml streams of megabytes.
Note: Instance of Smooks is thread safe and should be static as it's initialization per request basis is expensive. Here is is kept inside method scope to minimize changes.
Extending app with xml service
- Define http post route from /order to implementation method saveXml() at Application class in file conf/routes as "POST /order controllers.Application.saveXml()"
- Create body for method Application.saveXML ()
- Add XML body parser
- Get xml, transform it, save resulting object
- As all of this is bit much with exception handling just peek code https://github.com/nikkijuk/smooks-play2-xml-to-jpa/blob/master/smooks-play2-xml-to-jpa/app/controllers/Application.java
http://nikkijuk.blogspot.de/2013/08/simple-java-xml-web-services.html
Web user interface concept
As with xml service there's
- Route to define endpoint for clients
- Controller for logic
- Domain classes for model
But in addition to xml service we also have view template , which is used to render output to client.
Provide web user interface
- Add simple template which renders order list
- Define incoming parameter orders as list of CustomerOrder classes
- Add iteration over list of orders
@(orders: List[example.model.CustomerOrder])
@main("List of orders") {
<h1>List of orders</h1>
<ul>
@for(order <- orders){
<li>@order.id @order.header.date @order.header.customerNumber @order.header.customerName </li>
}
</ul>
}
Define controller method
- Add method connecting route to template
- Add logic to find all orders
- Add logic to render template
To make it readable I have kept method body on 2 lines.
public static Result list() {Define entry point
List<CustomerOrder> orders = CustomerOrder.find.findList();
return ok(list.render(orders));
}
- Add get request to route "GET /orders controllers.Application.list()"
See list of orders
- go to http://localhost:9000/orders
- You should see page with empty list
Prepare initial test data
- Create yaml file to conf directory with name default-orders.yml
- Fill file with one order having header and 2 line items
- See example at https://github.com/nikkijuk/smooks-play2-xml-to-jpa/blob/master/smooks-play2-xml-to-jpa/conf/default-orders.yml
Load test data
- Add static constructor to app.controllers.Application class
- Check that database is empty
- Use Yaml.load method of SnakeYaml to load default-orders.yml
- Result is at https://github.com/nikkijuk/smooks-play2-xml-to-jpa/blob/master/smooks-play2-xml-to-jpa/app/controllers/Application.java
static {
try {
if (Ebean.find(CustomerOrder.class).findRowCount() == 0) {
// load yaml data
Map<String, List<Object>> all = (Map<String, List<Object>>) Yaml
.load("default-orders.yml");
// Insert employees
Ebean.save(all.get("orders"));
Logger.info("Defaults added");
}
} catch (Exception e) {
Logger.error("Defaults couldn't be added "+e.getMessage(), e);
}
}
See list of orders
If you plan to extend test data changes are you need to check your Yaml is sane. let Yamllint to help http://yamllint.com/.- go to http://localhost:9000/orders
- You should now see initial test data
If you feel more academic interest then Yaml specification might help http://www.yaml.org/spec/1.2/spec.html
Testing with Soap UI & Browser
- Define post method of xml content to be sent to localhost:9000/order
- Get message from https://github.com/smooks/smooks/blob/v1.5.1/smooks-examples/xml-to-java/input-message.xml
- Send message to localhost:9000/order
- go to http://localhost:9000/orders
- You should now see initial test data and your own added order