DWR Presentation

Post on 15-Oct-2014

92 views 1 download

Tags:

Transcript of DWR Presentation

Ajax for Java developers… but without the suckage

Who is this jabronie? (aka: The Braggart Slide™)

• Frank W. Zammetti

• Developer/Lead/Architect/Whatever for PNC Global Investment Servicing

• Author of four books (fifth coming soon) and a couple of articles, tech reviewer on a number of other books

• Creator of Java Web Parts (APT most “famously”) and Struts-WS

• Current lead of DataVision

• One of the original developers of PocketHobbit

• Contributor to other OSS projects (Struts, Commons, etc.)

Open with a joke!

Cool, we can all go home now…

Ajax (for those residing in rock abodes)

• Asynchronous (almost always)

• JavaScript (almost always)

• XML (almost never)

• All about out-of-band requests and partial page loads

The beating of a dead Equine

• “Jesse” James Garrett, Adaptive Path, February 2005

• IT’S NOTHING NEW!!

• It’s about the concepts, not technology

• In some ways, it was the saviour of the Internet

That horse was askin’ for it!

Oh the humanity!

The big question: Why Ajax?

• Richer, more responsive UIs (RIAs)

• Reduced network utilization (careful!)

• Revolution in the guise of evolution

• It’s allows for a paradigm shift (once again, RIAs)

• Ajax isn’t just a communication mechanism any more (RIA == Ajax these days)

An RIA (and a real looker of an ET!)

Another RIA

One more for good measure

About that “suckage” I spoke of

• Ajax is hard to get right

• Many people don’t like doing JavaScript

• Requires a certain expertise that not every shop has

• Puts the focus on HTTP

Build or buy?

The star of the show: DWR

• Open-source, licensed under the ASL

• Member of the Dojo Foundation

• Java-only means no design compromises

• Implemented as a servlet, works fine in any container

• Minimizes JavaScript and deemphasizes servlet spec

• Makes calls to server-side code from JavaScript look the same as local calls

• Security is a core concept, not an afterthought

• Robust error handling

• Integration with many popular libraries and frameworks

• To put it simply: its an RPC mechanism for the Java webapps

RPC mystified

(someone told me lots of pictures in a slideshow is a good idea, even superfluous ones like this!)

DWR’s brand of RPC

Yeah, but what does it actually DO?

• Auto-generates a JavaScript proxy “stub” for a server-side Java object

• Handles marshalling of all inbound and outbound data

• Handles instantiation and calling appropriate methods of the server-side object

• Transparently handles all that icky Ajax stuff

DWR In a nutshell

The basics, part 1

• Just add some JARs (dwr.jar and commons-logging.jar) and a servlet entry:

<servlet> <servlet-name>dwr-invoker</servlet-name> <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>true</param-value> </init-param> <load-on-startup>1</load-on-startup></servlet><servlet-mapping> <servlet-name>dwr-invoker</servlet-name> <url-pattern>/dwr/*</url-pattern></servlet-mapping>

The basics, part 2

• dwr.xml configures it:<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 3.0//EN" "http://getahead.org/dwr/dwr30.dtd">

<dwr>

<allow> <create creator="new“ javascript=“MathDelegate”> <param name="class" value="app.MathDelegate" /> </create> </allow>

</dwr>

The basics, part 2a

The basics, part 3

• A server-side class to call on:

package app;

public class MathDelegate {

public int add(int a, int b) { return a + b; }

public int subtract(int a, int b) { return a - b; }

public int multiply(int a, int b) { return a * b; }

public int divide(int a, int b) { return a / b; }

}

The basics, part 4

Some client-side code to call it:

<html> <head> <script type="text/javascript" src="dwr/interface/MathDelegate.js"></script> <script type="text/javascript" src="dwr/engine.js"></script> <script> function $(inID) { return document.getElementById(inID); } function doMath() { MathDelegate[$("op").value]($("num1").value, $("num2").value, function(answer) { $("divAnswer").innerHTML = answer } ); } </script> </head> <body> <input type="text" id="num1" size="4">&nbsp; <select id="op"> <option value="add">+</option><option value="subtract">-</option> <option value="multiply">*</option><option value="divide">/</option> </select> &nbsp;<input type="text" id="num2" size="4"> <input type="button" value="=" onClick="doMath();"> &nbsp;<span id="divAnswer" style="font-size:18pt;">&nbsp;</span><br><br> </body></html>

Mmmm… pudding… AGHHAGGAGHAGAGA

(note to self: need to spell-check Homer Simpson biological drooling sound above)

math

Interfaces and the engine, and more

• dwr/interface/* are where the dynamically generated JavaScript proxy stubs corresponding to remotable server classes are served from

• engine.js, the client-side engine behind DWR, is mostly static but with some dynamic elements

• Optionally, there’s util.js

• Note that all of this is served by the DWR servlet

Call syntax

• Two ways… basic:MathDelegate.add(2, 2, function(serverResponse) { alert(serverResponse);});

• Call metadata object:MathDelegate.add(2, 2, { callback : function(serverResponse) { alert(serverResponse); }, errorHandler : function() { alert(“We’re boned!”); }});

Call syntax redux

• In general, use the call metadata object paradigm

• Allows for passing of additional information (error handlers, options)

• Basic approach might be more readable if you really only need a callback

The lost art of debugging

• Set debug servlet parameter to true

• http://server:port/context/dwr

• Lists all classes DWR can remote as well as test harnesses and even troubleshooting tips

• YOU’LL WANT TO TURN THIS OFF IN PRODUCTION

(anyone not impressed can leave their geek credentials at the door on the way out)

debugging

Yeah, neat, but what of performance?

• Lots of reflection magic

• Dynamic code generation

• You’d think performance would be terrible, but you’d be wrong!

• Interface files and util.js can be saved off and served from web server to take advantage of caching

• That DOES NOT work for engine.js!

Security-security-security-security… Security-security-security-security!

Threats aren’t always as cute as this

Only what you want

• Only classes listed in dwr.xml can be remoted

• Further, you can limit access at the method level:<create creator="new"> <param name="class" value="app.MathDelegate" /> <include method="MyMethod" /> <!-- All others now excluded --></create>

• By default, all methods are available

• In production you probably should always use the above paradigm

Only who you want

• Can limit access to J2EE roles at the method-level:

<create creator="new"> <param name="class" value="app.MathDelegate" /> <include method="MyMethod" /> <auth method="MyMethod" role="MyRole" /></create>

Creators

• Creators instantiate remotable objects

• Out of the box: new, none, spring, jsf, struts, pageflow, ejb3

• Provides for integration with other libraries

• Can trivially create your own

• new and none are the most commonly used

Converters

• Converters marshal beans from Java to JavaScript, and vice-versa

• Out of the box: boolean, byte, short, int, long, float, double, char, java.lang.Boolean, java.lang.Byte, java.lang.Short, java.lang.Integer, java.lang.Long, java.lang.Float, java.lang.Double, java.lang.Character, java.lang.BigInteger, java.lang.BigDecimal, java.lang.String, arrays, collections and maps of most types, enum, java.util.Date, java.sql.Date, java.sql.Time, java.sql.Timestamp

• bean and object converters, most frequently used (object works with data members directly, bean uses accessors/mutators)

• You can of course create your own too

Beans, beans, they’re good for your heart, part 1

package app;

public class SearchVO {

private String acctNum;

public void setAcctNum(String acctNum) { this.acctNum = acctNum; }

public String getAcctNum() { return this.acctNum; }

}

Beans, beans, they’re good for your heart, part 2

package app;

public class Account {

private String acctNum; private String shareholder; private Integer balance;

public void setAcctNumber(String acctNum) { this.acctNum = acctNum; } public String getAcctNum() { return this.acctNum; } public void setShareholder(String sh) { this.shareholder = sh; } public String getShareholder() { return this.shareholder; } public void setBalance(Integer balance) { this.balance = balance; } public Integer getBalance() { return this.balance; }

}

Beans, beans, they’re good for your heart, part 3

package app;

public class Processor {

public Account getAccount(SearchVO searchVO) {

Account account = new Account(); account.setAcctNum(searchVO.getAcctNum()); account.setShareholder("Tapping, Amanda"); account.setBalance(new Integer(25986)); return account;

}

}

Beans, beans, they’re good for your heart, part 4

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 3.0//EN" "http://getahead.org/dwr/dwr30.dtd">

<dwr>

<allow>

<convert converter="bean" match="app.SearchVO" /> <convert converter="bean" match="app.Account" />

<create creator="new“ javascript=“Processor”> <param name="class" value="app.Processor" /> </create>

</allow>

</dwr>

Beans, beans, they’re good for your heart, part 5

Here thar be JavaScript too:

<html><head> <script type="text/javascript" src="dwr/interface/Processor.js"></script> <script type="text/javascript" src="dwr/engine.js"></script> <script type="text/javascript" src="dwr/util.js"></script> <script> function doSearch() { var accountSearchVO = { acctNum : document.getElementById("acctNum").value }; Processor.getAccount(accountSearchVO, { callback : function(inAccount) { dwr.util.setValue("divAccountDetails", "Account Number: " + inAccount.acctNum + "<br>" + "Shareholder Name: " + inAccount.shareholder + "<br>" + "Current Balance: " + inAccount.balance, { escapeHtml : false } ); }}); } </script> </head><body> Account Number: <input type="text" id="acctNum"> <input type="button" value="Get Account" onClick="doSearch();"><br><br> <div id="divAccountDetails"></div> </body></html>

(* yes, that is indeed a cheap Watchmen reference!)

account

dwr.util

• General-purpose client utilities, mostly concerned with getting content into the DOM, in no way DWR-specific

• addOptions() – Add elements to lists (ol, ul, select)

• addRows() – Add rows to tables

• byId() – Shortcut to document.getElementById()

• getText() – Get text (not value) of an <option> element

• getValue()/getValues() – Get value of virtually any HTML element (it deals with the specifics of what “value” means for each)

• removeAllOptions() – Remove all <option> from a <select> or ol/ul element

• removeAllRows() – Remove all rows from a table

• setValue()/setValues() – The reverse of getValue()

• toDescriptiveString() – Output an object in a useful way

I hate it when a plan doesn’t come together

Error handling in DWR

• Can handle errors globally or on a per-call basis

• Warnings – Things you can usually ignore…dwr.engine.setWarningHandler(<function>)

• Errors - When DWR can tell you what went wrong (ex: server shuts down in the middle of servicing an AJAX request)…dwr.engine.setErrorHandler(<function>);

• Exceptions - Thrown from server and propagated to client. Must handle exceptions per-call…

Error Handling: The Next Generation™

Processor.getAccount(accountSearchVO, { callback : function(inAccount) { dwr.util.setValue("divAccountDetails", "Account Number: " + inAccount.acctNumber + "<br>" + "Shareholder Name: " + inAccount.shareholder + "<br>" + "Current Balance: " + inAccount.balance, { escapeHtml : false } ); }, errorHandler : function(errMsg, exception) { alert(dwr.util.toDescriptiveString(exception, 4)); }});

Making sense of exceptions

• Exceptions by default are not marshaled:<convert match="java.lang.Exception" converter="exception"> <param name="include" value="message,lineNumber" /></convert><convert match="java.lang.StackTraceElement" converter="bean" />

(I feel so cheap for making that Lost in Space reference... UNCLEAN! UNCLEAN!)

error

Are you part of the “XML is uncool” crowd?

• DWR also supports annotations:@RemoteProxypublic class WordsOfWisdom { @RemoteMethod public String getWisdom() { return “<insert something wisdom-y here>”; }}

• That’s not quite all there is to it:<servlet> <servlet-name>dwr-invoker</servlet-name> <servlet-class>org.directwebremoting.servlet.DWRServlet</servlet-class> <init-param><param-name>classes</param-name> <param-value>app.WordsOfWisdom</param-value> </init-param></servlet>

Using HTTP objects

• Sometimes a POJO isn’t enough (session):WebContext wc = WebContextFactory.get();HttpSession session = wc.getSession();

• But there’s a better way:Server:Public class Remote { public void myCallableMethod(String param, HttpSession session) { }}

Client:Remote.myCallableMethod(“test”, { callback : function(resp) { alert(resp); } });

• Reduced coupling to DWR… Syntax is cleaner… Less work (always good)• “Automagic” stuff isn’t usually good… Must call data meta-object

approach, so client-side code is arguably more verbose (slightly)

Call batching

• Can combine multiple operations in one:

dwr.engine.beginBath(); BatchCallClass.method1(callback1); BatchCallClass.method2(callback2); SomeOtherClass.method2(callback3);dwr.engine.endBath();function callback1() { alert(“callback1”); }function callback2() { alert(“callback2”); }function callback3() { alert(“callback3”); }

• One network request made• Order of calls and callback execution is guaranteed• Lets you keep code separated on the server while

maximizing runtime efficiency

Reading from other URLs

• Read response from a URL and return as string from a method:

public class URLReader { public String read() throws ServletException, IOException { return WebContextFactory.get().forwardToString(“/another.jsp”); }}

• Allows you to continue to use all the capabilities you’re used to in JSP

• JSP becomes a (powerful) templating technology only

• Ties you to DWR• Can only do forwards, so only the same context

Look out, here comes the Spring bandwagon!

• Can delegate to Spring for bean instantiation:dwr.xml:<create creator="spring" javascript="HelloHuman"> <param name="beanName" value="HelloHuman" /> <param name="location" value="spring-beans.xml" /></create>

spring-beans.xml:<?xml version="1.0" encoding="UTF-8"?>

<beans> <bean id="HelloHuman" class="app.HelloHuman" /></beans>

(awww, taking it APART is so much more fun!)

alltogether

When forward isn’t cool enough: Reverse Ajax

If pro is the opposite of con, then isn’t progress the opposite of congress?!?• “Server-push”

• Difficult to implement on your own (thank you DWR!)

• Passive and active modes gives you lots of flexibility

• Active mode (comet specfically) chews up threads on server and proxies

• Good in small-scale apps, use with extreme caution beyond that (depending on method)

• Special servers/extensions exist to alleviate scalability concerns (depending on method)

• Speaking of methods…

The three horsemen of the apocalypse, part 1: Piggybacking

Obviously not real “push”, but a decent approximation and often times “good enough”. Least resource-intensive (passive method).

The three horsemen of the apocalypse, part 2: Polling

Also not real “push”, but closer than piggybacking. Medium resource utilization (active method, but controllable).

Introducing New and Improved Comet!

(WARNING!! TRADEMARK INFRINGEMENT ALERT!!)

The three horsemen of the apocalypse, part 3a: Let’s try this Comet thing again

As close to real “push” as you’re going to get with HTTP. It’s nothing but a hack… but an extremely clever one! (true active method)

The mechanics of reverse Ajax: Piggybacking

• To activate piggybacking:

DO NOTHING!

• You can automatically piggyback on any incoming request

The mechanics of reverse Ajax: Polling

• To activate polling, add to client code:dwr.engine.setActiveReverseAjax(true);

• Then, add to DWR servlet config in web.xml:<init-param> <param-name>activeReverseAjaxEnabled</param-name> <param-value>true</param-value></init-param><init-param> <param-name>org.directwebremoting.extend.ServletLoadMonitor</param-name> <param-value>org.directwebremoting.impl.PollingServerLoadMonitor</param-value></init-param><init-param> <param-name>timeToNextPoll</param-name> <param-value>1000</param-value></init-param>

The mechanics of reverse Ajax: Comet

• To activate comet, add to client code:dwr.engine.setActiveReverseAjax(true);

• Then, add to DWR servlet config in web.xml:<init-param> <param-name>activeReverseAjaxEnabled</param-name> <param-value>true</param-value></init-param>

What about the J in JUG?!?

• From the server, do something like:Util.setValue("divRAResponse", "Hello!", true);

• All users currently viewing the page Ajax calls are sourced from will see “Hello!” in <divRAResponse>.

When you don’t want to talk to just anyone

• To do something for a single user:ScriptBuffer script = new ScriptBuffer();script.appendScript(“doSomething();");WebContext wc = WebContextFactory.get();ScriptSession scriptSession = wc.getScriptSession();scriptSession.addScript(script);

• The doSomething() Javascript function will be executed in the browser of the user belonging to the session associated with the request being serviced.

Running in the background

• You can spawn a background thread, and then do reverse Ajax from it

• Just need to cache the WebContext object

• Applies to communication with a single user as well as all users

• Careful! Spawning threads in a servlet contanier is bad, m’kay?

(except in the case of UFOs apparently)

ra_*

Binary files

• Can handle binary files (up and down)

• Can deal with byte[], java.awt.BufferedImage, java.io.inputStream or org.directwebtemoting.io.FileTransfer (gives access to filename, mime type and contents)

• Uploading: Easier than commons-fileupload or similar and can integrate with progress bar widgets

• Downloading: easier than creating a special PDF servlet or similar

• What, you don’t believe me? Ok, here you go:

Server:public class Remote { public void recieveFile(byte[] f) { /* Do something with contents. */ } public FileTransfer getFile() { // buf if a ByteArayOutputStream with the contents of a PDF in it. return new FileTransfer(“myFile.pdf”, “application/pdf”, buf.toByteArray()); }}

Client:<input type=“file” id=“myFile”>

// Send file.var f = dwr.util.getValue(“myFile”);Remote.recieveFile(f);

// Receive file.Remote.getFile(null, function(pdf) { dwr.engine.openInDownload(pdf);});

Poor man’s web services

• DWR supports JSON, JSON-P (so-called REST-based web services)

• Allows cross-domain access to DWR remotable classes

• Allows non-DWR clients to interact with DWR-exposed remotables

• Not perfect (manual Ajax, parameter naming, etc), but still very nice!

Server:public class Demo { public String sayHi(String name) { return “Hi there, “ + name; }}

Command Line:$ wget http://localhost/app/dwr/jsonp/Demo/sayHi?param0=Frank&callback=jsfunc

-> jsfunc(“Hi there, Frank”);

Varargs

• Avoids wrapping arguments in an array, collection or VO

• Can break edge cases when mixing servlet parameters and regular parameters

Server:public class VarArgClass { public void meth(String… arg) { /* Do something */ }}

Client:VarArgClass.meth(“Apollo”, “Starbuck”, “Boomer”);

Overloaded methods

• Prior to v3, overloaded methods were indeterminate (might get the right one, might not)

• Finally, in v3, true overloading support is present!

Server:public class Remoted { public void method(int num) { System.out.println(“num: “ + num); } public void method(String str) { System.out.println(“str: “ + str); }}

Client:Remoted.method(“I am a string”);Remoted.method(42);

Tie-ins with Dojo and others

• Dojo data stores

• Server-side manipulation of Dijits

• Deep Tibco General Interface (nearly full control of the entire UI from the server)

Reverse Ajax upgrades

• More scalable (maybe), more robust API

// Broadcast to all users currently viewing index.html page.Browser.withCurrentPage(“index.html”, new Runnable() { public void run() { Window.alert(“Hello”); }});

// Broadcast to everyone connected to DWR, regardless of current page.Browser.withAllSessions(…);

// Broadcast to a filtered subset of users.Browser.withFiltered(scriptSessionFilter, …);

// Broadcast to a specific user.Browser.withSession(sesssionID, …);

Reverse Ajax upgrades continued

• The server-side API is more robust, allowing for easier manipulation of the client from server code:

Element e = doc.createElement(“p”);ScriptSessions.addFunctionCall(“document.body.appendChild”, e);

ScriptSessions.addScript(“alert(‘Hello Zark!’);”);

Document.setCookie(new Cookie(“name”, “value”));

String[] opts = new String[] { “Kruger”, “Myers”, “Vorhees” };Util.addOptions(“li”, opts);

Effect.fade(“someID”);

In conclusion

• DWR kicks more arse than anything that has ever kicked arse before (and that’s only a slight exaggeration!)- simple, robust, powerful, secure*

• In short: if you’re a Java developer who does Ajax, and you do it with something other than DWR, you almost certainly want marijuana legalized!

• To sum it all up succinctly…

* and the opposite sex will find you more attractive for using it!

If you don’t use DWR, you might be as dumb as her

(at least she has looks… and BTW, mad props to Mario for not cracking up big-time!)

(And if you’d like to contact me after the show I’d be… err, surprised actually… but if you do: fzammetti@omnytex.com)