Pico Web Remoting (PWR) allows web requests to be bounded directly to methods in a class. All public methods are eligible, and method parameters are pulled from query-string or form post-fields for invocation. PWR is not a page technology as such, as it uses AJAX invoation of methods on the server side. As such you most likely going to use this technology from JavaScript, with a library like jQuery that makes calling server-side functions that return JSON replies easy.
Every Object gets a URL !We have tried to make PWR as transparent as possible. Thus objects that can be published via PWR, are POJOs. It is up to you whether you interface/impl separate them, but be reminded that by default all public methods will be published. As with all of the PicoContainer Web technologies, you will decide which scope a component belongs in - application, session or request - and compose them accordingly.
Publishing class and method names direct to the web, is alluring, but it is also dangerous. Do not think of this as publishing a domain model to the web, it is really a way for you to markup contacts in plain Java. It also suggests that you are going to want to have a different package, class and method naming design for the objects your publishing with PWR. Read about the example, then read Naming Considerations below
We forked an example of jQuery and Java together made for an IBM AlphaWorks article: Working with jQuery, Part 1: Bringing desktop applications to the browser. The original had a custom servlet to decode requests, invoke methods and craft JSON replies. We've essentially deleted that servlet and leveraged PWR instead.
In the demo webapp there a 'send' method on a class called 'Sent'. The class name and method name are encoded in the URL. The method parameters are pulled from the form post fields, and the reply is a JSON representation of the methods return type. Here are some screenshots from a Firefox plugin called HttpFox and how the web request maps to the source method. The case in question is for the 'send' function of the Simple email app:
MessageData is just a POJO:
A GET invocation is quite similar. Instead of form post-fields the method parameters are mapped to query string parameters.
If PWR encounters an exception during the processing of a method invocation, it takes the message text and makes it available to the client in the following style:
{ "ERROR": true, "message": "no such message ID" }
Composition of the webapp is done in an implementation of WebappComposer, like so:
package org.picocontainer.web.sample.jqueryemailui; import org.picocontainer.web.WebappComposer; import static org.picocontainer.web.StringFromRequest.addStringRequestParameters; import static org.picocontainer.web.IntFromRequest.addIntegerRequestParameters; import org.picocontainer.MutablePicoContainer; import static org.picocontainer.Characteristics.USE_NAMES; public class JQueryWebappComposer implements WebappComposer { // These will be cached and shared for all sessions unless otherwise specified public void composeApplication(MutablePicoContainer appContainer) { appContainer.addComponent(MessageStore.class, InMemoryMessageStore.class); } // These components can depend on each other or those from the application scope // They will also be cached per session unless otherwise specified public void composeSession(MutablePicoContainer sessionContainer) { // stateless } // These components can depend on each other or those from the session scope // They will also be cached per session unless otherwise specified public void composeRequest(MutablePicoContainer requestContainer) { // User is specified in a Cookie (after signin obviously) requestContainer.addAdapter(new User.FromCookie()); // Regular components. The public methods for which are published remotely requestContainer.as(USE_NAMES).addComponent(Auth.class); requestContainer.as(USE_NAMES).addComponent(Inbox.class); requestContainer.as(USE_NAMES).addComponent(Sent.class); } }
In the above there is a single application-scoped component, InMemoryMessageStore, for all users/sessions and requests to share. All other components are added at request scope, meaning they are only instantiated at the time of request and only if they are needed. The major components are Auth, Inbox and Sent. They have their public methods made available. Inbox and Sent methods are effectively not available until login has been performed via the Auth class. Once you have logged in, a cookie is set, thus User.FromCookie (a Provider) allows access to that. The parameters for the methods in Auth, Inbox and Send are made available via the addStringRequestParameters(..) and addIntegerRequestParameters(..) methods. These set up multiple Injectors for to allow the binding by name of things from query-string parameters or form post-fields.
Convention over Configuration !
PicoContainer Web Remoting(by default) does not have XML files like Spring, JBoss or XWork (Struts2). It also tries to be as transparent as possible - there are are no base classes to extend, no interfaces to implement, and no mandatory annotations, no bean definitions, no URL mappings, no property assignments - you just call methods on components from a web page in JavaScript. This makes it easy to test components in plain unit tests too.
Here is an fairly standard web.xml for a PicoWebRemoting webapp:
Pico Web Remoting Demo webapp-composer-class org.picocontainer.web.sample.jqueryemailui.JQueryDemoWebappComposer pwrFilter org.picocontainer.web.remoting.PicoWebRemotingServlet$ServletFilter pwrFilter /pwr/* org.picocontainer.web.PicoServletContainerListener pwrServlet org.picocontainer.web.remoting.JsonPicoWebRemotingServlet 1 scopes_to_publish session,request package_prefix_to_strip org.picocontainer.web.sample.jqueryemailui pwrServlet /pwr/* index.html
In the web.xml file, you can specify a 'scopes_to_publish' element that takes a comma separated list of scopes. One or more of 'application' , 'session' and 'request' (without the quotes). This listed will be analysed for publication as PWR invocable.
Only public method on components managed by PicoContainer are eligible. By default, all web methods (GET, POST, PUT, DELETE) are possible for a matching method. However there are annotations (in package org.picocontainer.web) with names @GET, @POST, @PUT, @DELETE, that restrict down possibilities. If you want a component's method to be only available for POST, put a @POST annotation above it. If you have a public method that you do not want to publish in a component that is otherwise published, use @NONE.
PWR by default will encode the whole class name including package. All dots in the package name are changed to slashes to make apparent directories, and the case of the Class name itself is preserved. With a web.xml element called package_prefix_to_strip it is possible to eliminate a prefix for all of the objects published. Thus, you could essentially turn http://mycompany.com/r/com/mycompany/store/Cart/buy into http://mycompany.com/r/store/Cart/buy if you wanted. The /r/ prefix is the mapping to the servlet that is configured in the web.xml - it could just as easily be /pwr/ or /rpc/ or /ajax/
You can have a suffix such as .ajax or .json at the end the method name. For example http://mycompany.com/r/com/mycompany/store/Cart/buy.json might be what you want. There is an servlet init-param for the servlet in web.xml element called suffix_to_strip that would specify '.json' in this case.
In our example above Auth (the class) appears as 'Auth' in any URLs. If you specify an init parameter of 'lower_case_path' as true, then you can have it as 'auth' in URLs
Many AJAX capable technologies are vulnerable to CSRF attacks. PWR is no different. If you are running your webapp publically, and there are functions that could be valuable for a hacker to invoke simulating genuine user action, you will want to guard them. With PWR, you would set something in the page, that would be send back to the server with each AJAX request. The Guard class would be instantiated before the potentially vulnerable method would be invoked. The guard class would be instantiated and depend on the guard parameter, ensuring that it could be verified and that an exception could be thrown:
requestContainer.addComponent("guard", MyGuard.class); requestContainer.as(GUARD, USE_NAMES).addComponent(VulnerableThing.class);
Binding to parameter from the HTTP Request is implicit as long as you use as(USE_NAMES) when adding components. If you the range of parameter names to be explicit, do the following in WebappComposition class
// these are pulled from query-strings (GET) or form-fields (POST) addStringRequestParameters(requestContainer, "to", "subject", "message", "view", "userName", "password", "userId", "sec"); addIntegerRequestParameters(requestContainer, "msgId");
Consider the this URL: http://mycompany.com/r/com/mycompany/store/ShoppingCart/addToCart
It is what would result from a Java class called ShoppingCart with a method called addToCart (in package com.mycompany.store). Java is often long winded like this. You are going to want to change the rules when working with PWR. ShoppingCart can be shortened to Cart as it is clear being in a directory (package) 'store' what it is. Method 'addToCart' is also too verbose if we consider the URL and the implicit association with 'Cart'. Thus 'buy' might be clearer intent.
In short, when naming packages, classes and methods for PWR, you should think about contracts between the client and the server app, and not simply publishing preexisting names.
It is also the case that you can only have one method of a particular name that is public, otherwise PWR will not know which ne to invoke for incoming requests.
The Maven based example project can be checked out with Subversion from here.
See downloads on how to download the Pico Web Remoting jar either using Maven or by downloading the full distribution.