Native REST Web Services
with Oracle 11g
CLOUG 2009
Marcelo F. Ochoa - Fac. Cs. Exactas - UNICEN - Tandil - Arg.
Agenda
REST
Restlet framework
XDB Restlet adapter
XDB Restlet adapter architecture
My first REST application
Performance test
Comparison against SOAP
Data Service Architecture and SOA last mile
Data Service example
DataServiceApplication
UserResource
OrdersResource
OrderResource
What is REST?
REpresentational State Transfer
Formalized by Roy Fielding in his PhD Dissertation
Primarily applicable to distributed hypermedia systems
Think of it as resource-orientation
Resources represent the domain concepts
Championed by the Web community
Amazon, Yahoo, Google
Adopted by some of the major vendors (Microsoft:WCF,
Sun:Jersey)
Positioned as an alternative to WS-* implementations
High impact in the community (Ruby on Rails, RESTlet)
Motivation for REST
Take advantage of what the Web does well
Simplicity
Scalability
Performance
Ease of use
So much nicer than the alternatives
SOAP & WS - *
Unifies Web Sites and Web Services into consistent Web
Applications
A Style, Not a Standard
REST guides the use of standards
Examples:
HTTP (Connector)
URI (Resource)
XML, HTML, GIF, etc. (Representations)
text/xml, text/html, image/gif, etc. (Media types)
The Web is a REST system
REST Architectural Style
Composition of styles that gains their benefits:
Client-Server - separation of concerns, scalability
Layered – allows intermediaries (proxies, firewalls)
without affecting interfaces
Stateless – scalability
Cacheable – reduces payload & latency
Pipe-and-Filter – dynamic component connection
REST at glance
Cartoon by Eric Lemerdy (http://eric.lemerdy.free.fr)
Resources
A Resource should be a fixed target of a URI
The URI-to-Resource mapping shouldn't
change, but the representation can
Resources may map to multiple
representations, called variants
Example: png, gif, jpg are variant
representations of an image
Content negotiation selects the best
variant
Uniform interface
Resources are manipulated by HTTP
methods
GET – retrieve a resource
PUT – create a resource
POST – update (create if necessary) a
resource
DELETE – delete a resource
Cacheability
A cache can return copy in response to a GET,
therefore prefer GET over POST
What is Restlet?
An open source REST framework for Java
A good mapping of REST principles
Founded by Jérôme Louvel,
Noelios Consulting, Paris, France
http://www.restlet.org/
Built in response to:
Need for a simple, RESTful web application framework
Servlet limitations
Restlet Framework
Restlet API – Supports REST call
handling
Extensions – For integrating
external technologies (JDBC,
JSON, alternate containers,
connectors, template engines, etc.)
SPI – Plugin point for alternate
implementations
Restlet Implementation –
Currently just Noelios Engine
Application
Restlet API
SPI XDB
Restlet Implementation
Extensions
Restlet framework
Representations
XDB Adapter
XdbServerServlet extends ServerServlet
A Servlet 2.2 compliant connector
XdbServletCall extends HttpServerCall
A Servlet 2.2 version of ServletCall class
XdbServletConverter extends HttpServerConverter
Converter of low-level HTTP server calls into high-level
uniform calls
XdbServletWarClient extends Client
Connector acting as a WAR client for a Servlet Application. Support
XMLDB repository browsing
XdbServletWarClientHelper extends ServletWarClientHelper
Local client connector based on a XMLDB repository. Translate:
URL: file:///$HOME/myPath/myFile.ext -> XDB: /home/SCOTT/myPath/myFile.ext
URL: war:///myPath/myFile.ext -> XDB: /home/SCOTT/wars/HelloRestlet/myPath/myFile.
ext
Servlet running with PUBLIC grants run with an effective user ANONYMOUS
XDB Servlet adapter architecture
HTTP presentation over
TCP/IP is used to
support native REST
WS
XDB Adapter application stack
Client alternatives:
an AJAX application
(GWT , DOJO Toolkit)
a RIA application
(Flex, Lazlo, etc.)
a middle tier
consuming REST WS
(REST client stack)
Client side:
Application
Context's client
dispatcher
HTTP client connector
HTTP protocol
TCP layer
IP layer
Server side:
Resource
Router
Services (decompression,
range, tunnel, etc.)
HTTP server connector
(XDB)
HTTP protocol
TCP layer
IP layer
Installing XDB Restlet Adapter
Using Reslet SVN 1.2M4 (SQLNet string test )
# cd reslet/build
# ant
# cd dist/classic/restlet-1.2snapshot/src/org.restlet.ext.xdb/resources/
# cat ~/build.properties
sqlnet.string=test
jdbc.username=RESTLET
jdbc.password=RESTLET
jdbc.sysusr=sys
jdbc.syspwd=change_on_install
# ant all
Buildfile: build.xml
create-schema:
.....
BUILD SUCCESSFUL
Total time: 1 minute 58 seconds
My first REST application
Extracted from Restlet tutorial 12 (automatically installed)
package org.restlet.example.tutorial ;
public class Part12 extends Application {
@Override
public Restlet createRoot() {
// Create a router
final Router router = new Router(getContext());
// Attach the resources to the router
router.attach("/users/{user }", UserResource .class);
router.attach("/users/{user }/orders", OrdersResource .class);
router.attach("/users/{user }/orders/{order }", OrderResource .class);
// Return the root router
return router;
}
}
Resources
UserResource
public class UserResource extends Resource {
String userName;
Object user;
public UserResource(Context context, Request request, Response response) {
super(context, request, response);
this.userName = (String) request.getAttributes().get("user ");
this.user = null; // Could be a lookup to a domain object.
// Here we add the representation variants exposed
getVariants().add(new Variant(MediaType.TEXT_PLAIN));
}
@Override
public Representation represent(Variant variant) throws ResourceException {
Representation result = null;
if (variant.getMediaType().equals(MediaType.TEXT_PLAIN)) {
result = new StringRepresentation("Account of user ""
+ this.userName + """);
}
return result;
}
}
Resources - cont.
OrdersResource
public class OrdersResource extends UserResource {
public OrdersResource(Context context, Request request, Response response) {
super(context, request, response);
}
@Override
public Representation represent(Variant variant) throws ResourceException {
Representation result = null;
if (variant.getMediaType().equals(MediaType.TEXT_PLAIN)) {
result = new StringRepresentation("Orders of user ""
+ this.userName + """);
}
return result;
}
}
Resources - cont.
OrderResource
public class OrderResource extends UserResource {
String orderId;
Object order;
public OrderResource(Context context, Request request, Response response) {
super(context, request, response);
this.orderId = (String) request.getAttributes().get("order ");
this.order = null; // Could be a lookup to a domain object.
}
@Override
public Representation represent(Variant variant) throws ResourceException {
Representation result = null;
if (variant.getMediaType().equals(MediaType.TEXT_PLAIN)) {
result = new StringRepresentation("Order "" + this.orderId
+ "" for user "" + this.userName + """);
}
return result;
}
}
Loading demos
Already done during installation step
#ant load-server-side-demos
Buildfile: build.xml
load-server-side-demos:
[java] arguments: '-nodefiner' '-r' '-v' '-s' '-g' 'public' '-u'
'RESTLET/***@test ' '../../../lib/org.restlet.example.jar'
.....
[exec] SQL> Disconnected from Oracle Database 11g Release
11.1.0.6.0 - Production
BUILD SUCCESSFUL
Total time: 14 seconds
Registering a REST Application
rem http://localhost:8080/userapp/users/scott/orders/300
DECLARE
configxml SYS.XMLType;
begin
dbms_xdb.deleteServletMapping('UsersRestlet');
dbms_xdb.deleteServlet('UsersRestlet');
dbms_xdb.addServlet(name=>'UsersRestlet',language=>'Java',
class=>'org.restlet.ext.xdb.XdbServerServlet ',
dispname=>'Restlet Servlet',schema=>'PUBLIC');
SELECT INSERTCHILDXML(xdburitype('/xdbconfig.xml ').getXML(),
'/xdbconfig/sysconfig/protocolconfig/httpconfig/webappconfig/servletconfig/servlet-list/servlet[servlet-
name="UsersRestlet"]',
'init-param',
XMLType('<init-param xmlns="http://xmlns.oracle.com/xdb/xdbconfig.xsd">
<param-name>org.restlet.application</param-name>
<param-value>RESTLET :org.restlet.example.tutorial.Part12 </param-value>
<description>REST User Application</description>
</init-param>'),'xmlns="http://xmlns.oracle.com/xdb/xdbconfig.xsd"') INTO configxml
FROM DUAL;
dbms_xdb.cfg_update(configxml);
dbms_xdb.addServletSecRole(SERVNAME => 'UsersRestlet',
ROLENAME => 'PUBLIC',ROLELINK => 'PUBLIC');
dbms_xdb.addServletMapping('/userapp/ *','UsersRestlet');
commit;
end;
Testing your REST application
Test using command line (telnet)
# telnet localhost 8080
Trying 127.0.0.1...
Connected to mochoa (127.0.0.1).
Escape character is '^]'.
GET /userapp/users/scott/orders/300 HTTP/1.0
Host: localhost:8080
HTTP/1.1 200 OK
MS-Author-Via: DAV
DAV: 1,2,<http://www.oracle.com/xdb/webdav/props>
Date: Thu, 02 Apr 2009 21:52:45 GMT
Server: Noelios-Restlet-Engine/1.2snapshot
Accept-Ranges: bytes
Content-Type: text/plain; charset=ISO-8859-1
Vary: Accept-Charset, Accept-Encoding, Accept-Language, Accept
Content-Length: 28
Order "300" for user "scott"
Benchmarking REST applications
Adding caching behavior
Add an expiration time now plus 10 seconds
@Override
public Representation represent(Variant variant) throws
ResourceException {
Representation result = null;
if (variant.getMediaType().equals(MediaType.TEXT_PLAIN)) {
result = new StringRepresentation("Order "" + this.orderId
+ "" for user "" + this.userName + """);
}
Date expirationDate = new Date(System.currentTimeMillis()+10000);
result.setExpirationDate(expirationDate);
return result;
}
Benchmarking caching behavior
Comparing against a SOAP application
Example application
create or replace and compile java source named "my.OrderCalculator" as
package my;
import java.util.logging.Level;
import java.util.logging.Logger;
public class OrderCalculator {
/**
* Java Util Logging variables and default values
*/
private static Logger logger = null;
/**
* Constant used to get Logger name
*/
static final String CLASS_NAME = OrderCalculator.class.getName();
static {
logger = Logger.getLogger(CLASS_NAME);
logger.setLevel(Level.ALL);
}
public static String getOrder(String userName, int orderId) {
logger.entering(CLASS_NAME,"getOrder",new Object [] {userName,new Integer(orderId)});
logger.exiting(CLASS_NAME,"getOrder","Order '"+orderId+"' for user '"+userName+"'");
return "Order '"+orderId+"' for user '"+userName+"'";
}
}
Defining a Call Spec
PLSQL Call Spec to provide execution as stored procedure
CREATE OR REPLACE PACKAGE orders_calculator AUTHID CURRENT_USER AS
FUNCTION getOrder(user_name IN VARCHAR2, order_id IN NUMBER) RETURN VARCHAR2 as
LANGUAGE JAVA NAME
'my.OrderCalculator.getOrder(java.lang.String, int) return java.lang.String';
END orders_calculator;
POST message to call above procedure with SOAP sintax
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://xmlns.
oracle.com/orawsv/SCOTT/ORDERS_CALCULATOR/GETORDER">
<env:Header/>
<env:Body>
<ns1:SVARCHAR2-GETORDERInput>
<ns1:USER_NAME-VARCHAR2-IN>scott</ns1:USER_NAME-VARCHAR2-IN>
<ns1:ORDER_ID-NUMBER-IN>300</ns1:ORDER_ID-NUMBER-IN>
</ns1:SVARCHAR2-GETORDERInput>
</env:Body>
</env:Envelope>
SOAP performance
Current SOA landscape
Source: http://soa.sys-con.com/node/620374
Data Services technology
Source: http://soa.sys-con.com/node/620374
Data Service example
Key technologies used
XDB Restlet adapter
XMLDB repository as persistent layer
Java in the database
XdbRepresentation
Oracle AQ for faster insert
XQuery
Think Data Services as data abstraction layer, not as simple
CRUD services
Data Service Application
public class DataServiceApplication
extends Application {
@Override
public Restlet createRoot() {
// Create a router
final Router router = new Router(getContext());
// Attach the resources to the router
router.attach("/users/{user}", UserResource.class);
router.attach("/users/{user}/orders", OrdersResource.class);
router.attach("/users/{user}/orders/{order}", OrderResource.class);
// Return the root router
return router;
}
}
Data Service Application UserResource
public class UserResource extends Resource {
...
public UserResource(Context context, Request request, Response response) {
super(context, request, response);
this.userName = (String)request.getAttributes().get("user");
..
try {
..
preparedstatement =
this.conn.prepareStatement("select XMLElement("User"," +
"XMLAttributes(? as "UserName",? as "UserID",SYSDATE as "Created"))" +
" from dual");
...
if (resultset.next()) {
user = resultset.getObject(1);
..
setAvailable(false);
}
...
} finally {
XdbServerServlet.closeDbResources(preparedstatement, resultset);
}
// Here we add the representation variants exposed
getVariants().add(new Variant(MediaType.TEXT_XML));
}
}
UserResource - GET
public Representation represent(Variant variant) throws ResourceException {
Representation result = null;
if (variant.getMediaType().equals(MediaType.TEXT_XML)) {
if (user instanceof XMLType)
result =
new XdbRepresentation(variant.getMediaType(), (XMLType)this.user);
else
generateErrorRepresentation("Returned object is not XMLType",
"", this.getResponse());
}
Date expirationDate = new Date(System.currentTimeMillis()+10000);
result.setExpirationDate(expirationDate);
return result;
}
Sample Output
GET /ds/users/sbell HTTP/1.0
Authorization: Basic c2NvdHQ6dGlnZXI=
Host: localhost:8080
HTTP/1.1 200 OK
MS-Author-Via: DAV
DAV: 1,2,<http://www.oracle.com/xdb/webdav/props>
Date: Sat, 04 Apr 2009 17:59:59 GMT
Server: Noelios-Restlet-Engine/1.2snapshot
Accept-Ranges: bytes
Content-Type: text/xml
Vary: Accept-Charset, Accept-Encoding, Accept-Language, Accept
Expires: Sat, 04 Apr 2009 18:00:09 GMT
<User UserName="sbell" UserID="SBELL" Created="2009-04-04"></User>
UserResource - POST handler
public void acceptRepresentation(Representation entity) {
int i = 0;
XMLType order;
PreparedStatement preparedstatement = null;
try {
order = XMLType.createXML(this.conn, entity.getStream());
preparedstatement = this.conn.prepareStatement(enqueueStmt);
preparedstatement.setObject(1, order);
preparedstatement.setString(2,schUrl);
i = preparedstatement.executeUpdate();
this.conn.commit();
String orderId =
order.extract("/PurchaseOrder/Reference/text()", null).getStringVal();
getResponse().setStatus(Status.SUCCESS_CREATED);
getResponse().setLocationRef(getRequest().getResourceRef() + "/" +
orderId.substring(this.userName.length()+1));
getResponse().setEntity(order.getStringVal(),
MediaType.TEXT_XML);
} catch (SQLException sqe) {
...
}
Enqueue operation:
enqueueStmt =
" dbms_aq.enqueue(queue_name => 'PO$Q',n" +
...
" payload => message.createSchemaBasedXML(?),n" +
...
"end;n";
UserResource - Sample POST
POST /ds/users/sbell/orders HTTP/1.0
Host: localhost:8080
Authorization: Basic c2NvdHQ6dGlnZXI=
Content-Type: text/xml; charset=ISO8859_1
Content-Length: 844
<PurchaseOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http:
//localhost:8080/home/SCOTT/poSource/xsd/purchaseOrder.xsd">
<Reference>SBELL-20030409123336231PDT</Reference>
<Actions>
<Action>
<User>SVOLLMAN</User>
</Action>
</Actions>
<Reject/>
<Requestor>Sarah J. Bell</Requestor>
<User>SBELL</User>
<CostCenter>S30</CostCenter>
<ShippingInstructions>
<name>Sarah J. Bell</name>
<address>400 Oracle Parkway......</address>
<telephone>650 506 7400</telephone>
</ShippingInstructions>
<SpecialInstructions>COD</SpecialInstructions>
<LineItems>
<LineItem ItemNumber="1">
<Description>Juliet of the Spirits</Description>
<Part Id="37429165829" UnitPrice="29.95" Quantity="2"/>
</LineItem>
</LineItems>
</PurchaseOrder>
OrdersResoure
public class OrdersResource extends UserResource {
public OrdersResource(Context context, Request request,
Response response) {
super(context, request, response);
ResultSet resultset = null;
PreparedStatement preparedstatement = null;
try {
// Gets a list of Orders for given User
preparedstatement =
this.conn.prepareStatement("SELECT XMLQuery('<Orders>n" +
" {for $i in ora:view("PURCHASEORDER")/PurchaseOrdern" +
" where $i/User = $useridn" +
" returnn" +
" <PurchaseOrder>n" +
" {$i/Reference}n" +
" </PurchaseOrder>n" +
" }n" +
" </Orders>' PASSING ? as "userid"n" +
" RETURNING CONTENT) AS orders FROM DUAL");
preparedstatement.setString(1, this.userName.toUpperCase());
resultset = preparedstatement.executeQuery();
if (resultset.next()) {
orders = resultset.getObject(1);
...
} finally {
XdbServerServlet.closeDbResources(preparedstatement, resultset);
}
}
OrdersResource - Represent
public Representation represent(Variant variant) throws ResourceException {
Representation result = null;
if (variant.getMediaType().equals(MediaType.TEXT_XML)) {
if (this.orders instanceof XMLType)
result =
new XdbRepresentation(variant.getMediaType(), (XMLType)this.orders);
else
generateErrorRepresentation("Returned object is not XMLType",
"", this.getResponse());
}
return result;
}
Sample Output
GET /ds/users/sbell/orders HTTP/1.0
Authorization: Basic c2NvdHQ6dGlnZXI=
Host: localhost:8080
HTTP/1.1 200 OK
MS-Author-Via: DAV
DAV: 1,2,<http://www.oracle.com/xdb/webdav/props>
Date: Sat, 04 Apr 2009 18:17:09 GMT
Server: Noelios-Restlet-Engine/1.2snapshot
Accept-Ranges: bytes
Content-Type: text/xml
Vary: Accept-Charset, Accept-Encoding, Accept-Language, Accept
<Orders><PurchaseOrder><Reference>SBELL-20030409123336231PDT</Reference>
<Requestor>Sarah J. Bell</Requestor>
<CostCenter>S30</CostCenter>
</PurchaseOrder></Orders>
OrderResource
public class OrderResource extends UserResource {
public OrderResource(Context context, Request request, Response response) {
super(context, request, response);
this.orderId = (String)request.getAttributes().get("order");
..
try {
// Gets a list of Orders for given User
preparedstatement = this.conn.prepareStatement(selectStmt);
preparedstatement.setString(1,
this.userName.toUpperCase() + "-" + this.orderId);
resultset = preparedstatement.executeQuery();
if (resultset.next()) {
order = resultset.getObject(1);
...
} else {
setModifiable(true);
setAvailable(false);
}
} catch (SQLException sqe) {
..
} finally {
XdbServerServlet.closeDbResources(preparedstatement, resultset);
}
}
selectStmt:
public static final String selectStmt =
"select * from " + table + " where existsNode(object_value," +
"'/PurchaseOrder[Reference="'||?||'"]')=1";
OrderResource - DELETE handler
public void removeRepresentations() throws ResourceException {
int i = 0;
if (this.order != null) {
// Remove the item from the list.
PreparedStatement preparedstatement = null;
try {
// Gets a list of Orders for given User
preparedstatement = this.conn.prepareStatement(deleteStmt);
preparedstatement.setString(1,
this.userName.toUpperCase() + "-" +
this.orderId);
i = preparedstatement.executeUpdate();
this.conn.commit();
// Tells the client that the request has been successfully fulfilled.
getResponse().setStatus(Status.SUCCESS_MULTI_STATUS);
getResponse().setEntity("Deleted " + i + " record(s).",
MediaType.TEXT_PLAIN);
} catch (SQLException sqe) {
...
} finally {
XdbServerServlet.closeDbResources(preparedstatement, null);
}
...
}
}
deleteStmt:
public static final String deleteStmt =
"delete from " + table + " where existsNode(object_value," +
"'/PurchaseOrder[Reference="'||?||'"]')=1";
OrderResource - DELETE - sample
DELETE /ds/users/sbell/orders/20030409123336231PDT HTTP/1.0
Authorization: Basic c2NvdHQ6dGlnZXI=
Host: localhost:8080
HTTP/1.1 207 Unknown error
MS-Author-Via: DAV
DAV: 1,2,<http://www.oracle.com/xdb/webdav/props>
Date: Sat, 04 Apr 2009 22:54:09 GMT
Server: Noelios-Restlet-Engine/1.2snapshot
Accept-Ranges: bytes
Content-Type: text/plain; charset=ISO-8859-1
Content-Length: 20
Deleted 1 record(s).
OrderResource - PUT handler
public void storeRepresentation(Representation entity) throws ResourceException {
int i = 0;
PreparedStatement preparedstatement = null;
boolean newOrder = (this.order == null);
try {
this.order = XMLType.createXML(this.conn, entity.getStream());
if (newOrder) {
preparedstatement = this.conn.prepareStatement(enqueueStmt);
preparedstatement.setObject(1, this.order);
preparedstatement.setString(2,schUrl);
} else {
preparedstatement = this.conn.prepareStatement(updateStmt);
preparedstatement.setObject(1, this.order);
preparedstatement.setString(2,
this.userName.toUpperCase() + "-" +
this.orderId);
}
i = preparedstatement.executeUpdate();
this.conn.commit();
...
} finally {
XdbServerServlet.closeDbResources(preparedstatement, null);
}
}
updateStmt:
public static final String updateStmt =
"update " + table + " set object_value = ?" +
" where existsNode(object_value," +
"'/PurchaseOrder[Reference="'||?||'"]')=1";
OrderResource - PUT - sample
PUT /ds/users/sbell/order/20030409123336231PDT HTTP/1.0
Host: localhost:8080
Authorization: Basic c2NvdHQ6dGlnZXI=
Content-Type: text/xml; charset=ISO8859_1
Content-Length: 1005
<PurchaseOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://localhost:
8080/home/SCOTT/poSource/xsd/purchaseOrder.xsd">
<Reference>SBELL20030409123336231PDT</Reference>
...
<LineItems>
<LineItem ItemNumber="1">
<Description>Juliet of the Spirits</Description>
<Part Id="37429165829" UnitPrice="29.95" Quantity="2"/>
</LineItem>
<LineItem ItemNumber="2">
<Description>Carl Th. Dreyer - My Metier</Description>
<Part Id="37429161425" UnitPrice="0.0" Quantity="3"/>
</LineItem>
</LineItems>
</PurchaseOrder>
HTTP/1.1 201 Created
MS-Author-Via: DAV
DAV: 1,2,<http://www.oracle.com/xdb/webdav/props>
Date: Sat, 04 Apr 2009 23:14:20 GMT
Location: http://localhost/ds/users/sbell/orders/20030409123336231PDT
Server: Noelios-Restlet-Engine/1.2snapshot
Accept-Ranges: bytes
Content-Type: text/plain; charset=ISO-8859-1
Content-Length: 20
Updated 1 record(s).
Q & A
Thank you
Need more information?
OracleJVM and Java Stored Procedures
http://www.oracle.com/technology/tech/java/jsp/index.html
Java Developer's Guide Java
http://tahiti.oracle.com/pls/db111/to_toc?pathname=java.111/b31225/toc.htm
XMLDB Developer's Guide
http://tahiti.oracle.com/pls/db111/to_toc?pathname=appdev.111/b28369/toc.htm
Restlet Framework
http://www.restlet.org/
XDB Restlet Adapter
http://wiki.restlet.org/docs_1.1/13-restlet/28-restlet/84-restlet.html
Data Service Example code
http://dbprism.cvs.sourceforge.net/viewvc/dbprism/DataService/
My blog
http://marceloochoa.blogspot.com/

Native REST Web Services with Oracle 11g

  • 1.
    Native REST WebServices with Oracle 11g CLOUG 2009 Marcelo F. Ochoa - Fac. Cs. Exactas - UNICEN - Tandil - Arg.
  • 2.
    Agenda REST Restlet framework XDB Restletadapter XDB Restlet adapter architecture My first REST application Performance test Comparison against SOAP Data Service Architecture and SOA last mile Data Service example DataServiceApplication UserResource OrdersResource OrderResource
  • 3.
    What is REST? REpresentationalState Transfer Formalized by Roy Fielding in his PhD Dissertation Primarily applicable to distributed hypermedia systems Think of it as resource-orientation Resources represent the domain concepts Championed by the Web community Amazon, Yahoo, Google Adopted by some of the major vendors (Microsoft:WCF, Sun:Jersey) Positioned as an alternative to WS-* implementations High impact in the community (Ruby on Rails, RESTlet)
  • 4.
    Motivation for REST Takeadvantage of what the Web does well Simplicity Scalability Performance Ease of use So much nicer than the alternatives SOAP & WS - * Unifies Web Sites and Web Services into consistent Web Applications
  • 5.
    A Style, Nota Standard REST guides the use of standards Examples: HTTP (Connector) URI (Resource) XML, HTML, GIF, etc. (Representations) text/xml, text/html, image/gif, etc. (Media types) The Web is a REST system
  • 6.
    REST Architectural Style Compositionof styles that gains their benefits: Client-Server - separation of concerns, scalability Layered – allows intermediaries (proxies, firewalls) without affecting interfaces Stateless – scalability Cacheable – reduces payload & latency Pipe-and-Filter – dynamic component connection
  • 7.
    REST at glance Cartoonby Eric Lemerdy (http://eric.lemerdy.free.fr) Resources A Resource should be a fixed target of a URI The URI-to-Resource mapping shouldn't change, but the representation can Resources may map to multiple representations, called variants Example: png, gif, jpg are variant representations of an image Content negotiation selects the best variant Uniform interface Resources are manipulated by HTTP methods GET – retrieve a resource PUT – create a resource POST – update (create if necessary) a resource DELETE – delete a resource Cacheability A cache can return copy in response to a GET, therefore prefer GET over POST
  • 8.
    What is Restlet? Anopen source REST framework for Java A good mapping of REST principles Founded by Jérôme Louvel, Noelios Consulting, Paris, France http://www.restlet.org/ Built in response to: Need for a simple, RESTful web application framework Servlet limitations
  • 9.
    Restlet Framework Restlet API– Supports REST call handling Extensions – For integrating external technologies (JDBC, JSON, alternate containers, connectors, template engines, etc.) SPI – Plugin point for alternate implementations Restlet Implementation – Currently just Noelios Engine Application Restlet API SPI XDB Restlet Implementation Extensions
  • 10.
  • 11.
  • 12.
    XDB Adapter XdbServerServlet extendsServerServlet A Servlet 2.2 compliant connector XdbServletCall extends HttpServerCall A Servlet 2.2 version of ServletCall class XdbServletConverter extends HttpServerConverter Converter of low-level HTTP server calls into high-level uniform calls XdbServletWarClient extends Client Connector acting as a WAR client for a Servlet Application. Support XMLDB repository browsing XdbServletWarClientHelper extends ServletWarClientHelper Local client connector based on a XMLDB repository. Translate: URL: file:///$HOME/myPath/myFile.ext -> XDB: /home/SCOTT/myPath/myFile.ext URL: war:///myPath/myFile.ext -> XDB: /home/SCOTT/wars/HelloRestlet/myPath/myFile. ext Servlet running with PUBLIC grants run with an effective user ANONYMOUS
  • 13.
    XDB Servlet adapterarchitecture HTTP presentation over TCP/IP is used to support native REST WS
  • 14.
    XDB Adapter applicationstack Client alternatives: an AJAX application (GWT , DOJO Toolkit) a RIA application (Flex, Lazlo, etc.) a middle tier consuming REST WS (REST client stack) Client side: Application Context's client dispatcher HTTP client connector HTTP protocol TCP layer IP layer Server side: Resource Router Services (decompression, range, tunnel, etc.) HTTP server connector (XDB) HTTP protocol TCP layer IP layer
  • 15.
    Installing XDB RestletAdapter Using Reslet SVN 1.2M4 (SQLNet string test ) # cd reslet/build # ant # cd dist/classic/restlet-1.2snapshot/src/org.restlet.ext.xdb/resources/ # cat ~/build.properties sqlnet.string=test jdbc.username=RESTLET jdbc.password=RESTLET jdbc.sysusr=sys jdbc.syspwd=change_on_install # ant all Buildfile: build.xml create-schema: ..... BUILD SUCCESSFUL Total time: 1 minute 58 seconds
  • 16.
    My first RESTapplication Extracted from Restlet tutorial 12 (automatically installed) package org.restlet.example.tutorial ; public class Part12 extends Application { @Override public Restlet createRoot() { // Create a router final Router router = new Router(getContext()); // Attach the resources to the router router.attach("/users/{user }", UserResource .class); router.attach("/users/{user }/orders", OrdersResource .class); router.attach("/users/{user }/orders/{order }", OrderResource .class); // Return the root router return router; } }
  • 17.
    Resources UserResource public class UserResourceextends Resource { String userName; Object user; public UserResource(Context context, Request request, Response response) { super(context, request, response); this.userName = (String) request.getAttributes().get("user "); this.user = null; // Could be a lookup to a domain object. // Here we add the representation variants exposed getVariants().add(new Variant(MediaType.TEXT_PLAIN)); } @Override public Representation represent(Variant variant) throws ResourceException { Representation result = null; if (variant.getMediaType().equals(MediaType.TEXT_PLAIN)) { result = new StringRepresentation("Account of user "" + this.userName + """); } return result; } }
  • 18.
    Resources - cont. OrdersResource publicclass OrdersResource extends UserResource { public OrdersResource(Context context, Request request, Response response) { super(context, request, response); } @Override public Representation represent(Variant variant) throws ResourceException { Representation result = null; if (variant.getMediaType().equals(MediaType.TEXT_PLAIN)) { result = new StringRepresentation("Orders of user "" + this.userName + """); } return result; } }
  • 19.
    Resources - cont. OrderResource publicclass OrderResource extends UserResource { String orderId; Object order; public OrderResource(Context context, Request request, Response response) { super(context, request, response); this.orderId = (String) request.getAttributes().get("order "); this.order = null; // Could be a lookup to a domain object. } @Override public Representation represent(Variant variant) throws ResourceException { Representation result = null; if (variant.getMediaType().equals(MediaType.TEXT_PLAIN)) { result = new StringRepresentation("Order "" + this.orderId + "" for user "" + this.userName + """); } return result; } }
  • 20.
    Loading demos Already doneduring installation step #ant load-server-side-demos Buildfile: build.xml load-server-side-demos: [java] arguments: '-nodefiner' '-r' '-v' '-s' '-g' 'public' '-u' 'RESTLET/***@test ' '../../../lib/org.restlet.example.jar' ..... [exec] SQL> Disconnected from Oracle Database 11g Release 11.1.0.6.0 - Production BUILD SUCCESSFUL Total time: 14 seconds
  • 21.
    Registering a RESTApplication rem http://localhost:8080/userapp/users/scott/orders/300 DECLARE configxml SYS.XMLType; begin dbms_xdb.deleteServletMapping('UsersRestlet'); dbms_xdb.deleteServlet('UsersRestlet'); dbms_xdb.addServlet(name=>'UsersRestlet',language=>'Java', class=>'org.restlet.ext.xdb.XdbServerServlet ', dispname=>'Restlet Servlet',schema=>'PUBLIC'); SELECT INSERTCHILDXML(xdburitype('/xdbconfig.xml ').getXML(), '/xdbconfig/sysconfig/protocolconfig/httpconfig/webappconfig/servletconfig/servlet-list/servlet[servlet- name="UsersRestlet"]', 'init-param', XMLType('<init-param xmlns="http://xmlns.oracle.com/xdb/xdbconfig.xsd"> <param-name>org.restlet.application</param-name> <param-value>RESTLET :org.restlet.example.tutorial.Part12 </param-value> <description>REST User Application</description> </init-param>'),'xmlns="http://xmlns.oracle.com/xdb/xdbconfig.xsd"') INTO configxml FROM DUAL; dbms_xdb.cfg_update(configxml); dbms_xdb.addServletSecRole(SERVNAME => 'UsersRestlet', ROLENAME => 'PUBLIC',ROLELINK => 'PUBLIC'); dbms_xdb.addServletMapping('/userapp/ *','UsersRestlet'); commit; end;
  • 22.
    Testing your RESTapplication Test using command line (telnet) # telnet localhost 8080 Trying 127.0.0.1... Connected to mochoa (127.0.0.1). Escape character is '^]'. GET /userapp/users/scott/orders/300 HTTP/1.0 Host: localhost:8080 HTTP/1.1 200 OK MS-Author-Via: DAV DAV: 1,2,<http://www.oracle.com/xdb/webdav/props> Date: Thu, 02 Apr 2009 21:52:45 GMT Server: Noelios-Restlet-Engine/1.2snapshot Accept-Ranges: bytes Content-Type: text/plain; charset=ISO-8859-1 Vary: Accept-Charset, Accept-Encoding, Accept-Language, Accept Content-Length: 28 Order "300" for user "scott"
  • 23.
  • 24.
    Adding caching behavior Addan expiration time now plus 10 seconds @Override public Representation represent(Variant variant) throws ResourceException { Representation result = null; if (variant.getMediaType().equals(MediaType.TEXT_PLAIN)) { result = new StringRepresentation("Order "" + this.orderId + "" for user "" + this.userName + """); } Date expirationDate = new Date(System.currentTimeMillis()+10000); result.setExpirationDate(expirationDate); return result; }
  • 25.
  • 26.
    Comparing against aSOAP application Example application create or replace and compile java source named "my.OrderCalculator" as package my; import java.util.logging.Level; import java.util.logging.Logger; public class OrderCalculator { /** * Java Util Logging variables and default values */ private static Logger logger = null; /** * Constant used to get Logger name */ static final String CLASS_NAME = OrderCalculator.class.getName(); static { logger = Logger.getLogger(CLASS_NAME); logger.setLevel(Level.ALL); } public static String getOrder(String userName, int orderId) { logger.entering(CLASS_NAME,"getOrder",new Object [] {userName,new Integer(orderId)}); logger.exiting(CLASS_NAME,"getOrder","Order '"+orderId+"' for user '"+userName+"'"); return "Order '"+orderId+"' for user '"+userName+"'"; } }
  • 27.
    Defining a CallSpec PLSQL Call Spec to provide execution as stored procedure CREATE OR REPLACE PACKAGE orders_calculator AUTHID CURRENT_USER AS FUNCTION getOrder(user_name IN VARCHAR2, order_id IN NUMBER) RETURN VARCHAR2 as LANGUAGE JAVA NAME 'my.OrderCalculator.getOrder(java.lang.String, int) return java.lang.String'; END orders_calculator; POST message to call above procedure with SOAP sintax <env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://xmlns. oracle.com/orawsv/SCOTT/ORDERS_CALCULATOR/GETORDER"> <env:Header/> <env:Body> <ns1:SVARCHAR2-GETORDERInput> <ns1:USER_NAME-VARCHAR2-IN>scott</ns1:USER_NAME-VARCHAR2-IN> <ns1:ORDER_ID-NUMBER-IN>300</ns1:ORDER_ID-NUMBER-IN> </ns1:SVARCHAR2-GETORDERInput> </env:Body> </env:Envelope>
  • 28.
  • 29.
    Current SOA landscape Source:http://soa.sys-con.com/node/620374
  • 30.
    Data Services technology Source:http://soa.sys-con.com/node/620374
  • 31.
    Data Service example Keytechnologies used XDB Restlet adapter XMLDB repository as persistent layer Java in the database XdbRepresentation Oracle AQ for faster insert XQuery Think Data Services as data abstraction layer, not as simple CRUD services
  • 32.
    Data Service Application publicclass DataServiceApplication extends Application { @Override public Restlet createRoot() { // Create a router final Router router = new Router(getContext()); // Attach the resources to the router router.attach("/users/{user}", UserResource.class); router.attach("/users/{user}/orders", OrdersResource.class); router.attach("/users/{user}/orders/{order}", OrderResource.class); // Return the root router return router; } }
  • 33.
    Data Service ApplicationUserResource public class UserResource extends Resource { ... public UserResource(Context context, Request request, Response response) { super(context, request, response); this.userName = (String)request.getAttributes().get("user"); .. try { .. preparedstatement = this.conn.prepareStatement("select XMLElement("User"," + "XMLAttributes(? as "UserName",? as "UserID",SYSDATE as "Created"))" + " from dual"); ... if (resultset.next()) { user = resultset.getObject(1); .. setAvailable(false); } ... } finally { XdbServerServlet.closeDbResources(preparedstatement, resultset); } // Here we add the representation variants exposed getVariants().add(new Variant(MediaType.TEXT_XML)); } }
  • 34.
    UserResource - GET publicRepresentation represent(Variant variant) throws ResourceException { Representation result = null; if (variant.getMediaType().equals(MediaType.TEXT_XML)) { if (user instanceof XMLType) result = new XdbRepresentation(variant.getMediaType(), (XMLType)this.user); else generateErrorRepresentation("Returned object is not XMLType", "", this.getResponse()); } Date expirationDate = new Date(System.currentTimeMillis()+10000); result.setExpirationDate(expirationDate); return result; } Sample Output GET /ds/users/sbell HTTP/1.0 Authorization: Basic c2NvdHQ6dGlnZXI= Host: localhost:8080 HTTP/1.1 200 OK MS-Author-Via: DAV DAV: 1,2,<http://www.oracle.com/xdb/webdav/props> Date: Sat, 04 Apr 2009 17:59:59 GMT Server: Noelios-Restlet-Engine/1.2snapshot Accept-Ranges: bytes Content-Type: text/xml Vary: Accept-Charset, Accept-Encoding, Accept-Language, Accept Expires: Sat, 04 Apr 2009 18:00:09 GMT <User UserName="sbell" UserID="SBELL" Created="2009-04-04"></User>
  • 35.
    UserResource - POSThandler public void acceptRepresentation(Representation entity) { int i = 0; XMLType order; PreparedStatement preparedstatement = null; try { order = XMLType.createXML(this.conn, entity.getStream()); preparedstatement = this.conn.prepareStatement(enqueueStmt); preparedstatement.setObject(1, order); preparedstatement.setString(2,schUrl); i = preparedstatement.executeUpdate(); this.conn.commit(); String orderId = order.extract("/PurchaseOrder/Reference/text()", null).getStringVal(); getResponse().setStatus(Status.SUCCESS_CREATED); getResponse().setLocationRef(getRequest().getResourceRef() + "/" + orderId.substring(this.userName.length()+1)); getResponse().setEntity(order.getStringVal(), MediaType.TEXT_XML); } catch (SQLException sqe) { ... } Enqueue operation: enqueueStmt = " dbms_aq.enqueue(queue_name => 'PO$Q',n" + ... " payload => message.createSchemaBasedXML(?),n" + ... "end;n";
  • 36.
    UserResource - SamplePOST POST /ds/users/sbell/orders HTTP/1.0 Host: localhost:8080 Authorization: Basic c2NvdHQ6dGlnZXI= Content-Type: text/xml; charset=ISO8859_1 Content-Length: 844 <PurchaseOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http: //localhost:8080/home/SCOTT/poSource/xsd/purchaseOrder.xsd"> <Reference>SBELL-20030409123336231PDT</Reference> <Actions> <Action> <User>SVOLLMAN</User> </Action> </Actions> <Reject/> <Requestor>Sarah J. Bell</Requestor> <User>SBELL</User> <CostCenter>S30</CostCenter> <ShippingInstructions> <name>Sarah J. Bell</name> <address>400 Oracle Parkway......</address> <telephone>650 506 7400</telephone> </ShippingInstructions> <SpecialInstructions>COD</SpecialInstructions> <LineItems> <LineItem ItemNumber="1"> <Description>Juliet of the Spirits</Description> <Part Id="37429165829" UnitPrice="29.95" Quantity="2"/> </LineItem> </LineItems> </PurchaseOrder>
  • 37.
    OrdersResoure public class OrdersResourceextends UserResource { public OrdersResource(Context context, Request request, Response response) { super(context, request, response); ResultSet resultset = null; PreparedStatement preparedstatement = null; try { // Gets a list of Orders for given User preparedstatement = this.conn.prepareStatement("SELECT XMLQuery('<Orders>n" + " {for $i in ora:view("PURCHASEORDER")/PurchaseOrdern" + " where $i/User = $useridn" + " returnn" + " <PurchaseOrder>n" + " {$i/Reference}n" + " </PurchaseOrder>n" + " }n" + " </Orders>' PASSING ? as "userid"n" + " RETURNING CONTENT) AS orders FROM DUAL"); preparedstatement.setString(1, this.userName.toUpperCase()); resultset = preparedstatement.executeQuery(); if (resultset.next()) { orders = resultset.getObject(1); ... } finally { XdbServerServlet.closeDbResources(preparedstatement, resultset); } }
  • 38.
    OrdersResource - Represent publicRepresentation represent(Variant variant) throws ResourceException { Representation result = null; if (variant.getMediaType().equals(MediaType.TEXT_XML)) { if (this.orders instanceof XMLType) result = new XdbRepresentation(variant.getMediaType(), (XMLType)this.orders); else generateErrorRepresentation("Returned object is not XMLType", "", this.getResponse()); } return result; } Sample Output GET /ds/users/sbell/orders HTTP/1.0 Authorization: Basic c2NvdHQ6dGlnZXI= Host: localhost:8080 HTTP/1.1 200 OK MS-Author-Via: DAV DAV: 1,2,<http://www.oracle.com/xdb/webdav/props> Date: Sat, 04 Apr 2009 18:17:09 GMT Server: Noelios-Restlet-Engine/1.2snapshot Accept-Ranges: bytes Content-Type: text/xml Vary: Accept-Charset, Accept-Encoding, Accept-Language, Accept <Orders><PurchaseOrder><Reference>SBELL-20030409123336231PDT</Reference> <Requestor>Sarah J. Bell</Requestor> <CostCenter>S30</CostCenter> </PurchaseOrder></Orders>
  • 39.
    OrderResource public class OrderResourceextends UserResource { public OrderResource(Context context, Request request, Response response) { super(context, request, response); this.orderId = (String)request.getAttributes().get("order"); .. try { // Gets a list of Orders for given User preparedstatement = this.conn.prepareStatement(selectStmt); preparedstatement.setString(1, this.userName.toUpperCase() + "-" + this.orderId); resultset = preparedstatement.executeQuery(); if (resultset.next()) { order = resultset.getObject(1); ... } else { setModifiable(true); setAvailable(false); } } catch (SQLException sqe) { .. } finally { XdbServerServlet.closeDbResources(preparedstatement, resultset); } } selectStmt: public static final String selectStmt = "select * from " + table + " where existsNode(object_value," + "'/PurchaseOrder[Reference="'||?||'"]')=1";
  • 40.
    OrderResource - DELETEhandler public void removeRepresentations() throws ResourceException { int i = 0; if (this.order != null) { // Remove the item from the list. PreparedStatement preparedstatement = null; try { // Gets a list of Orders for given User preparedstatement = this.conn.prepareStatement(deleteStmt); preparedstatement.setString(1, this.userName.toUpperCase() + "-" + this.orderId); i = preparedstatement.executeUpdate(); this.conn.commit(); // Tells the client that the request has been successfully fulfilled. getResponse().setStatus(Status.SUCCESS_MULTI_STATUS); getResponse().setEntity("Deleted " + i + " record(s).", MediaType.TEXT_PLAIN); } catch (SQLException sqe) { ... } finally { XdbServerServlet.closeDbResources(preparedstatement, null); } ... } } deleteStmt: public static final String deleteStmt = "delete from " + table + " where existsNode(object_value," + "'/PurchaseOrder[Reference="'||?||'"]')=1";
  • 41.
    OrderResource - DELETE- sample DELETE /ds/users/sbell/orders/20030409123336231PDT HTTP/1.0 Authorization: Basic c2NvdHQ6dGlnZXI= Host: localhost:8080 HTTP/1.1 207 Unknown error MS-Author-Via: DAV DAV: 1,2,<http://www.oracle.com/xdb/webdav/props> Date: Sat, 04 Apr 2009 22:54:09 GMT Server: Noelios-Restlet-Engine/1.2snapshot Accept-Ranges: bytes Content-Type: text/plain; charset=ISO-8859-1 Content-Length: 20 Deleted 1 record(s).
  • 42.
    OrderResource - PUThandler public void storeRepresentation(Representation entity) throws ResourceException { int i = 0; PreparedStatement preparedstatement = null; boolean newOrder = (this.order == null); try { this.order = XMLType.createXML(this.conn, entity.getStream()); if (newOrder) { preparedstatement = this.conn.prepareStatement(enqueueStmt); preparedstatement.setObject(1, this.order); preparedstatement.setString(2,schUrl); } else { preparedstatement = this.conn.prepareStatement(updateStmt); preparedstatement.setObject(1, this.order); preparedstatement.setString(2, this.userName.toUpperCase() + "-" + this.orderId); } i = preparedstatement.executeUpdate(); this.conn.commit(); ... } finally { XdbServerServlet.closeDbResources(preparedstatement, null); } } updateStmt: public static final String updateStmt = "update " + table + " set object_value = ?" + " where existsNode(object_value," + "'/PurchaseOrder[Reference="'||?||'"]')=1";
  • 43.
    OrderResource - PUT- sample PUT /ds/users/sbell/order/20030409123336231PDT HTTP/1.0 Host: localhost:8080 Authorization: Basic c2NvdHQ6dGlnZXI= Content-Type: text/xml; charset=ISO8859_1 Content-Length: 1005 <PurchaseOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://localhost: 8080/home/SCOTT/poSource/xsd/purchaseOrder.xsd"> <Reference>SBELL20030409123336231PDT</Reference> ... <LineItems> <LineItem ItemNumber="1"> <Description>Juliet of the Spirits</Description> <Part Id="37429165829" UnitPrice="29.95" Quantity="2"/> </LineItem> <LineItem ItemNumber="2"> <Description>Carl Th. Dreyer - My Metier</Description> <Part Id="37429161425" UnitPrice="0.0" Quantity="3"/> </LineItem> </LineItems> </PurchaseOrder> HTTP/1.1 201 Created MS-Author-Via: DAV DAV: 1,2,<http://www.oracle.com/xdb/webdav/props> Date: Sat, 04 Apr 2009 23:14:20 GMT Location: http://localhost/ds/users/sbell/orders/20030409123336231PDT Server: Noelios-Restlet-Engine/1.2snapshot Accept-Ranges: bytes Content-Type: text/plain; charset=ISO-8859-1 Content-Length: 20 Updated 1 record(s).
  • 44.
  • 45.
    Need more information? OracleJVMand Java Stored Procedures http://www.oracle.com/technology/tech/java/jsp/index.html Java Developer's Guide Java http://tahiti.oracle.com/pls/db111/to_toc?pathname=java.111/b31225/toc.htm XMLDB Developer's Guide http://tahiti.oracle.com/pls/db111/to_toc?pathname=appdev.111/b28369/toc.htm Restlet Framework http://www.restlet.org/ XDB Restlet Adapter http://wiki.restlet.org/docs_1.1/13-restlet/28-restlet/84-restlet.html Data Service Example code http://dbprism.cvs.sourceforge.net/viewvc/dbprism/DataService/ My blog http://marceloochoa.blogspot.com/