Post on 20-Jan-2015
description
Native REST Web Services with Oracle 11g
CLOUG 2009
Marcelo F. Ochoa - Fac. Cs. Exactas - UNICEN - Tandil - Arg.
AgendaRESTRestlet framework XDB Restlet adapterXDB Restlet adapter architectureMy first REST application
Performance test Comparison against SOAP
Data Service Architecture and SOA last mileData Service example
DataServiceApplicationUserResourceOrdersResourceOrderResource
What is REST?
REpresentational State TransferFormalized by Roy Fielding in his PhD DissertationPrimarily applicable to distributed hypermedia systemsThink 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-* implementationsHigh impact in the community (Ruby on Rails, RESTlet)
Motivation for REST
Take advantage of what the Web does wellSimplicityScalabilityPerformanceEase of use
So much nicer than the alternativesSOAP & WS - *
Unifies Web Sites and Web Services into consistent Web Applications
A Style, Not a Standard
REST guides the use of standardsExamples:
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, scalabilityLayered – allows intermediaries (proxies, firewalls) without affecting interfacesStateless – scalabilityCacheable – reduces payload & latencyPipe-and-Filter – dynamic component connection
REST at glanceCartoon by Eric Lemerdy (http://eric.lemerdy.free.fr)
ResourcesA Resource should be a fixed target of a URIThe URI-to-Resource mapping shouldn't change, but the representation can Resources may map to multiple representations, called variants
Example: png, gif, jpg are variantrepresentations of an imageContent negotiation selects the best variant
Uniform interfaceResources are manipulated by HTTPmethods
GET – retrieve a resourcePUT – create a resourcePOST – update (create if necessary) a resourceDELETE – delete a resource
CacheabilityA cache can return copy in response to a GET, therefore prefer GET over POST
What is Restlet?
An open source REST framework for JavaA good mapping of REST principlesFounded by Jérôme Louvel,Noelios Consulting, Paris, France http://www.restlet.org/Built in response to:
Need for a simple, RESTful web application frameworkServlet limitations
Restlet Framework
Restlet API – Supports REST call handlingExtensions – For integrating external technologies (JDBC,JSON, alternate containers, connectors, template engines, etc.)SPI – Plugin point for alternate implementationsRestlet Implementation – Currently just Noelios Engine
Application
Restlet API
SPI XDB
Restlet Implementation
Extensions
Restlet framework
Representations
XDB AdapterXdbServerServlet extends ServerServletA Servlet 2.2 compliant connectorXdbServletCall extends HttpServerCallA Servlet 2.2 version of ServletCall classXdbServletConverter extends HttpServerConverterConverter of low-level HTTP server calls into high-level uniform callsXdbServletWarClient extends ClientConnector acting as a WAR client for a Servlet Application. Support XMLDB repository browsingXdbServletWarClientHelper extends ServletWarClientHelperLocal 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:ApplicationContext's client dispatcherHTTP client connectorHTTP protocolTCP layerIP layer
Server side:ResourceRouterServices (decompression,range, tunnel, etc.)HTTP server connector (XDB)HTTP protocol TCP layerIP 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.propertiessqlnet.string=testjdbc.username=RESTLETjdbc.password=RESTLETjdbc.sysusr=sysjdbc.syspwd=change_on_install# ant allBuildfile: build.xmlcreate-schema:.....BUILD SUCCESSFULTotal 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; }}
ResourcesUserResource
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 SUCCESSFULTotal time: 14 seconds
Registering a REST Applicationrem http://localhost:8080/userapp/users/scott/orders/300DECLARE 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 8080Trying 127.0.0.1...Connected to mochoa (127.0.0.1).Escape character is '^]'.GET /userapp/users/scott/orders/300 HTTP/1.0Host: localhost:8080
HTTP/1.1 200 OKMS-Author-Via: DAVDAV: 1,2,<http://www.oracle.com/xdb/webdav/props>Date: Thu, 02 Apr 2009 21:52:45 GMTServer: Noelios-Restlet-Engine/1.2snapshotAccept-Ranges: bytesContent-Type: text/plain; charset=ISO-8859-1Vary: Accept-Charset, Accept-Encoding, Accept-Language, AcceptContent-Length: 28
Order "300" for user "scott"
Benchmarking REST applications
Adding caching behavior
Add an expiration time now plus 10 seconds@Overridepublic 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 applicationExample application
create or replace and compile java source named "my.OrderCalculator" aspackage 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 procedureCREATE 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
Data Services technology
Source: http://soa.sys-con.com/node/620374
Data Service example
Key technologies usedXDB Restlet adapterXMLDB repository as persistent layerJava in the databaseXdbRepresentationOracle AQ for faster insertXQuery
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 UserResourcepublic 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.0Authorization: Basic c2NvdHQ6dGlnZXI=Host: localhost:8080
HTTP/1.1 200 OKMS-Author-Via: DAVDAV: 1,2,<http://www.oracle.com/xdb/webdav/props>Date: Sat, 04 Apr 2009 17:59:59 GMTServer: Noelios-Restlet-Engine/1.2snapshotAccept-Ranges: bytesContent-Type: text/xmlVary: Accept-Charset, Accept-Encoding, Accept-Language, AcceptExpires: 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 POSTPOST /ds/users/sbell/orders HTTP/1.0Host: localhost:8080Authorization: Basic c2NvdHQ6dGlnZXI=Content-Type: text/xml; charset=ISO8859_1Content-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\")/PurchaseOrder\n" + " where $i/User = $userid\n" + " return\n" + " <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 OutputGET /ds/users/sbell/orders HTTP/1.0Authorization: Basic c2NvdHQ6dGlnZXI=Host: localhost:8080
HTTP/1.1 200 OKMS-Author-Via: DAVDAV: 1,2,<http://www.oracle.com/xdb/webdav/props>Date: Sat, 04 Apr 2009 18:17:09 GMTServer: Noelios-Restlet-Engine/1.2snapshotAccept-Ranges: bytesContent-Type: text/xmlVary: Accept-Charset, Accept-Encoding, Accept-Language, Accept
<Orders><PurchaseOrder><Reference>SBELL-20030409123336231PDT</Reference><Requestor>Sarah J. Bell</Requestor><CostCenter>S30</CostCenter></PurchaseOrder></Orders>
OrderResourcepublic 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 - sampleDELETE /ds/users/sbell/orders/20030409123336231PDT HTTP/1.0Authorization: Basic c2NvdHQ6dGlnZXI=Host: localhost:8080
HTTP/1.1 207 Unknown errorMS-Author-Via: DAVDAV: 1,2,<http://www.oracle.com/xdb/webdav/props>Date: Sat, 04 Apr 2009 22:54:09 GMTServer: Noelios-Restlet-Engine/1.2snapshotAccept-Ranges: bytesContent-Type: text/plain; charset=ISO-8859-1Content-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 - samplePUT /ds/users/sbell/order/20030409123336231PDT HTTP/1.0 Host: localhost:8080Authorization: Basic c2NvdHQ6dGlnZXI=Content-Type: text/xml; charset=ISO8859_1Content-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 CreatedMS-Author-Via: DAVDAV: 1,2,<http://www.oracle.com/xdb/webdav/props>Date: Sat, 04 Apr 2009 23:14:20 GMTLocation: http://localhost/ds/users/sbell/orders/20030409123336231PDTServer: Noelios-Restlet-Engine/1.2snapshotAccept-Ranges: bytesContent-Type: text/plain; charset=ISO-8859-1Content-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/