pREST Tutorial
REST
In today's IT world came a time when trends are changing radically in the Web usage. The Web as the most successful distribution platform of a hypermedia content in the world goes through a season when Rich Internet Applications (RIA, AJAX Web Applications) and REST Web Services get into the foreground. The common attribute of both is the REST.
REST - Representational State Transfer is a software style architecture used for distributed hypermedia systems like the World Wide Web. It was established in Roy Thomas Fielding's thesis "Architectural Styles and the Design of Network-based Software Architectures" . REST is dedicated for defining the way that a good designed Web Application, or Web service should work. From the REST viewpoint the Web Application is a net of Web pages, virtual state machines, through which the user browses thanks to links (state transferees) that always bring the following web page representing the next application state which is transferred to the user.
REST strictly defines collection of principles about net architecture which describes how are the Internet resources defined and addressed. Net architecture also describes Internet resources' URL, their representation and links among them. These principles cover main goals:
- Scalability of interacting components – variousness of server and client type
- Universality of interfaces – REST makes a better base for Web Services frameworks than technologies based on SOAP
- Independence of component load – variousness of server and client implementations
- Compatibility among intermediate components in order to reduce latency, to increase the level of security and to increase encapsulation - a usage of Web proxy and gates for encapsulation of non web systems
REST reached the goals by application of four limitations:
- Identification of sources with URI – sources are logical objects identified by URI
- Manipulation of sources only through their representation – sources are not accessed directly
- Self descriptive messages – REST dictate that HTTP message must be as self characterized as possible
- Hypermedia as the bearer of the application state – actual state of the application must be trapped in a hypertext document or representation
Although REST as a software architecture style is not a standard, it strongly supports a usage of standards as: HTTP, URL, XML/HTML/GIF/JPEG/..., MIME (text/xml, text/html, image/gif, image/jpeg, …). The REST philosophy defends existing principles and Web protocols that they are enough for Rich Web Service production. That means, that developers, who understand HTTP can implement Web Services without the need of using other tools, than they normally use for Web Application development. The key for REST methodology is to write Web Services with the usage of interfaces that are well known and extended. Any client or server application with the support of HTTP can possibly call these services.
IT companies which are involved in the market use REST API for their services for a long period of time. No doupt in fact that REST is a trend on creating Web Services simply and effectively.
- Lightweight – it does not need any special XML markup as SOAP does
- Human Readable Results
- Easy to build – it does not need any other special tools for their development
Nowadays the majority of Web Services published on the Internet are URI-based.
- The most AJAX Web applications are no more than an Internet browser client which communicates with URI-based REST Web Services
- The most biggest Web Applications on the Internet are technically identical to URI-based REST Web Services
If the REST did not work then no Web, nor Internet would work.
The architectural style REST introduce unified base for the development of functionally rich AJAX Rich Internet Applications, which hold properties and functionality of traditional desktop applications.
The RIA transfers an adaptation needed for the user interface to a Web client, at the same time when it keeps data which hold an application state on the Application Web Server side. The advantage of the RIA is the ability of desktop application running on a Web browser without the need of installation.
There is an important fact, that the RIA's presentation layer running in a browser can be changed at any time by the interface of standard desktop application, which can use as a data source some REST Web service such as the RIA (in a difference of database connection as it was done long time ago). In addition the RDA (Rich Desktop Application) has the advantage that the application logic is mostly located on the server where it can be better implemented and tested.
pREST
For some years we have felt the need of framework for simple development of Rich Internet and Intranet Applications together with Web Services. For adequate solution we have defined requirements what suitable framework should fulfill:
- Modularity – modularity of architectural framework
- Extensibility – simple ability of new technology integration
- Isolate development of
- presentation layer
- server back-end
- application logic
- Horizontal application development – so that common developer does not have to know all technologies used for the development. From this development way we expect a higher code quality
- Minimal amount of time for development in the framework – we expect time in hours
- The possibility of REST Web Application and Service development - AJAX, RIA
- The independence of the higher amount of technologies – simple maintenance and stability through a long period of time
- Framework should be platform independent – Java 5 and higher
Unfortunately none of existing Web frameworks which are up to standard of our defined requirements are not based on Java platform. New thoughts and technologies are born on platforms like Python, Ruby, which gave developers more live space for carrying out new ideas but for more code writing price (they are dynamically typed prototype languages). Even though they are far ahead from nowadays Web solution of prominent software companies, they cannot be used in the market (our consumer, our master), or their solution state cannot be marked as productive. Because of these reasons, we have decided to implement our own framework developed under the code name pREST. We have decided to use MVC design pattern, which makes the development of Web Application to be like the development of desktop application.
pREST Web framework insists on simplicity and development effects. It introduce the development way of:
- Traditional Web applications
- AJAX Web applications
pREST consists of two components: a server side component and a client side component.
Server side pREST component
The server side pREST component is a controller for the servlet container dedicated for effective development of Web services and Web Applications through the REST architectural style. It implements following attributes:
- Controller mapping
- Dynamic mapping of URL parameters
- Automatic mapping of Web inputs to native data types
- Automatic serialization of native data type outputs
- Simple interface for own extension implementation
- Declarative way in extension usage realized by Java annotations
- The set of extensions which modular append the functionality of the controller/filter
- The set of validator extensions which validate the syntax and semantics with the support of conversion into a native data type
- The isolation of a presentation layer from an application logic with a weak bond hold-back considering the usage of presentation technology
- Build-in support of intercepting for aspect-oriented programming
- Build-in support for internationalization (i18n) during the process of data validation
- Support for linear workflow
- The possibility of controller connection and disconnection (also through Web interface)
- Role based user access management
- The declarative way of documentation inside the code by Java annotations
- The Web Interface used for accessing documentation by the Java reflexions
- The testing possibility of implemented controllers, Web Services, with the support of build-in Web Interface
Client side pREST component
Client pREST side component is a JavaScript library used for effective implementation of the RIA. The library is independent on pREST component and it is possible to use it with a combination of any server side technology. It implements following attributes:
- Signal-Slot design pattern
- Object oriented API to visual components (tables, tabs, lists, ...)
- Object oriented API of HTML form components and validators
- AJAX support
- Logging API
- Development tools
pREST Project Creation
The pREST application is a Java Web application developed in the pREST Web framework running in a servlet container. For the application deployment we need Java 5 (or higher) and a any servlet container running on Java 5. In this tutorial we use reference implementation of servlet container Apache Tomcat (version 5 and more). For the project composition we use standard tool for Java project building - Ant.
The pREST project structure
The development of the application we start with the creation of the
project skeleton. Starting with a creation of tutorial folder
we continue with a creation of the following sub content:
tutorial |-- docs |-- lib |-- src |-- web | |-- META-INF | |-- WEB-INF | | `-- web.xml | `-- index.html |-- build.properties `-- build.xml
The meaning of the folders and files in the project:
- docs
- static documentation files, both API documentation (javadoc), administration documentation, user documentation and any other project documentation.
- lib
-
jar files required for application run. In our case we need just
prest.jar - src
- Java project source files
- web
-
static web content as HTML, JSP, or other (e.g. pictures). Despite that it has to contain
subfolders
META-INFandWEB-INF - build.xml
- Ant build file
- build.properties
-
build properties used by
build.xml - web.xml
- deployment descriptor of web application. It is located in web/WEB-INF.
- index.html
- HTML index page of the application.
For classic web application used to be deployed for web container the files
build.xml, build.properties,
web.xml and index.html lool like this:
build.xml
<project name="tutorial" default="install" basedir=".">
<property file="build.properties" />
<property file="${user.home}/build.properties" />
<property name="app.name" value="tutorial" />
<property name="app.path" value="/${app.name}" />
<property name="app.version" value="0.1-dev" />
<property name="build.home" value="${basedir}/build" />
<property name="catalina.home" value="../../../.." />
<property name="dist.home" value="${basedir}/dist" />
<property name="docs.home" value="${basedir}/docs" />
<property name="manager.url" value="http://localhost:8080/manager" />
<property name="src.home" value="${basedir}/src" />
<property name="web.home" value="${basedir}/web" />
<property name="lib.home" value="${basedir}/lib" />
<taskdef name="deploy" classname="org.apache.catalina.ant.DeployTask" />
<taskdef name="list" classname="org.apache.catalina.ant.ListTask" />
<taskdef name="reload" classname="org.apache.catalina.ant.ReloadTask" />
<taskdef name="undeploy" classname="org.apache.catalina.ant.UndeployTask" />
<property name="compile.debug" value="true" />
<property name="compile.deprecation" value="false" />
<property name="compile.optimize" value="true" />
<path id="compile.classpath">
<fileset dir="${catalina.home}/lib">
<include name="*.jar" />
</fileset>
<fileset dir="${lib.home}">
<include name="*.jar" />
</fileset>
</path>
<target name="all" depends="clean,compile" description="Clean build and dist directories, then compile" />
<target name="clean" description="Delete old build and dist directories">
<delete dir="${build.home}" />
<delete dir="${dist.home}" />
</target>
<target name="compile" depends="prepare" description="Compile Java sources">
<mkdir dir="${build.home}/WEB-INF/classes" />
<javac srcdir="${src.home}" destdir="${build.home}/WEB-INF/classes" debug="${compile.debug}" deprecation="${compile.deprecation}" optimize="${compile.optimize}">
<classpath refid="compile.classpath" />
</javac>
<copy todir="${build.home}/WEB-INF/classes">
<fileset dir="${src.home}" excludes="**/*.java" />
</copy>
</target>
<target name="dist" depends="compile,javadoc" description="Create binary distribution">
<mkdir dir="${dist.home}" />
<copy todir="${build.home}/docs">
<fileset dir="${docs.home}" />
</copy>
<jar jarfile="${dist.home}/${app.name}-${app.version}.war" basedir="${build.home}" />
</target>
<target name="install" depends="compile" description="Install application to servlet container">
<deploy url="${manager.url}" username="${manager.username}" password="${manager.password}" path="${app.path}" localWar="file://${build.home}" />
</target>
<target name="reinstall" depends="remove,compile" description="Reinstall application to servlet container">
<deploy url="${manager.url}" username="${manager.username}" password="${manager.password}" path="${app.path}" localWar="file://${build.home}" />
</target>
<target name="javadoc" depends="compile" description="Create Javadoc API documentation">
<mkdir dir="${build.home}/docs/api" />
<javadoc sourcepath="${src.home}" destdir="${build.home}/docs/api" packagenames="*">
<classpath refid="compile.classpath" />
</javadoc>
</target>
<target name="list" description="List installed applications on servlet container">
<list url="${manager.url}" username="${manager.username}" password="${manager.password}" />
</target>
<target name="prepare">
<mkdir dir="${build.home}" />
<mkdir dir="${build.home}/WEB-INF" />
<mkdir dir="${build.home}/WEB-INF/classes" />
<copy todir="${build.home}">
<fileset dir="${web.home}" />
</copy>
<mkdir dir="${build.home}/WEB-INF/lib" />
<copy todir="${build.home}/WEB-INF/lib">
<fileset dir="${basedir}/lib" />
</copy>
</target>
<target name="reload" depends="compile" description="Reload application on servlet container">
<reload url="${manager.url}" username="${manager.username}" password="${manager.password}" path="${app.path}" />
</target>
<target name="remove" description="Remove application on servlet container">
<undeploy url="${manager.url}" username="${manager.username}" password="${manager.password}" path="${app.path}" />
</target>
</project>
build.xml is a ant build file for building web application.
Its purpose is not only to build the project, but also to install/uninstall
the application, creating the API documentation, etc.
For complete list of targets use Ant with a parameter "-p".
$ ant -p Buildfile: build.xml Main targets: all Clean build and dist directories, then compile clean Delete old build and dist directories compile Compile Java sources dist Create binary distribution install Install application to servlet container javadoc Create Javadoc API documentation list List installed applications on servlet container reinstall Reinstall application to servlet container reload Reload application on servlet container remove Remove application on servlet container Default target: install
build.properties
app.path=/tutorial app.version=0.1 # Tomcat installation directory catalina.home=/usr/share/tomcat6 # Manager webapp url, username and password manager.url=http://localhost:8080/manager manager.username=user1 manager.password=password1
The meaning of the file build.properties is to provide
that properties included in build.xml, which are commonly
changed and their change is easier in a small separate file.
- app.path - path in the servlet container where we want the application to be installed. In our case we set the value to tutorial.
- catalina.home - path to the home folder of Apache Tomcat (e.g. "/usr/share/tomcat6")
- manager.url - URL address of Tomcat manager (e.g. "http://localhost:8080/manager"). Manager is a web service which puts web application into the servlet container
- manager.username - user name which is needed for the deployment by Tomcat manager (it is written in configuration file"../tomcat.XY/conf/tomcat-users.xml" and it is given a manager role.
- manager.password - the password corresponding to user name in parameter manager.username
web.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <session-config> <session-timeout>30</session-timeout><!-- 30 minutes --> </session-config> </web-app>
index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" > <head> <title>pREST Tutorial</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> </head> <body> <h1>pREST tutoriál</h1> </body> </html>
The index.html is application root index file.
Deployment of the pREST application into the container
This application can be deployed to servlet container in war
application package. This method is useful for business production
deployment. War package can be deployed into the dist
folder by command:
ant dist
During the development, when we have servlet container installed on developing computer, it is better to deploy the application by following command.
ant install
If we need to change the old version for the new one, we can do it by this command.
ant reinstall
The quicker alternative, when no full application replacement will be done, but only its reload.
ant reload
During the process of deployment the web application is created
into build sub folder in project folder and it is
put into the container directly from this folder.
Finally we can test the functionality at this address
http://localhost:8080/tutorial/
when the page index.html should appear.
The creation of pREST application
To demonstrate the pREST application, we will create an pREST application with the "Tutorial" name with a simple controller. In next steps we are going to describe every part, which are needed for the application creation.
The modification of build.properties file
In the first step we set the application build properties in
build.properties file.
Deployment of the framework library "prest.jar"
Since we create pREST application it is required to put the pREST
library prest.jar which contains pREST framework into
the lib folder.
The actual address structure now looks like this:
tutorial |-- docs |-- lib | `-- prest.jar |-- src |-- web | |-- META-INF | |-- WEB-INF | | `-- web.xml | `-- index.html |-- build.properties `-- build.xml
The pREST application deployment
pREST application is represented by a class, which extends the class
prest.core.Application. This class is deployed into
new servlet container by an adapter which is represented by a servlet
filter.
Let's create for tutorial project an application class TutorialApplication.java,
which we will put into tutorial package. The class and the package
will be created in src folder. The class TutorialApplication.java
looks like this:
package tutorial;
import prest.core.Application;
import prest.core.ApplicationException;
public class TutorialApplication extends Application {
@Override
public void initialize() throws ApplicationException {
}
}
The actual address structure now looks like this:
tutorial |-- docs |-- lib | `-- prest.jar |-- src | `-- tutorial | `-- TutorialApplication.java |-- web | |-- META-INF | |-- WEB-INF | | `-- web.xml | `-- index.html |-- build.properties `-- build.xml
Now, we deploy the pREST application represented by a class
tutorial.TutorialApplication into
the Web application of the servlet container using the adapter
which is the servlet filter prest.core.ServletFilter.
Filter are deployed into the Web application's container by declaring
and mapping him in WEB-INF/web.xml file.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <session-config> <session-timeout>30</session-timeout><!-- 30 minutes --> </session-config> <filter> <filter-name>ServletFilter</filter-name> <filter-class >prest.core.ServletFilter</filter-class> <init-param> <param-name>Application</param-name> <param-value>tutorial.TutorialApplication</param-value> </init-param> </filter> <filter-mapping> <filter-name>ServletFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
The only required parameter of an adapter represented by
prest.core.ServletFilter filter is a parameter called
Application. The value of the parameter is
a cannonical name in the main pREST application class.
In our case it is tutorial.TutorialApplication.
The deployment itself can be set in the web.xml
file.
Controller creation
The next class, which needs to be created is a class HelloController.java,
which represents the controller. The controller's class will be placed into
the new package tutorial.controllers. The class
HelloController.java now looks like this:
package tutorial.controllers;
import prest.core.annotations.Action;
import prest.web.WebController;
public class HelloController extends WebController {
@Action(name="hello-world")
public String helloWorld() {
setContentType(ContentType.TEXT_PLAIN_UTF8);
return "Hello world!";
}
}
Action annotation is used for mapping action name (part of URL) and HTTP method into the pREST controller Java method.
The controller is a class which resolves prest.core.Controller
or some of its successors (for example prest.web.WebController).
In our case we declare public method helloWorld() which
returns string "Hello world".
Mapping controller in one specific URL address
To expose the controller and it's methods on some concrete
URL address, it is required to map this controller in the
Application class.
The mapping itself is realized by calling the method
mount() in the application class. The method
mount() has the following syntax:
mount(String location, Controller controller)
- location - the part of URL address to which the controller should map.
- controller - the instance of class representing controller, which will be mounted on concrete URL address.
How does the controller map into concrete URL address?
The controller maps into concrete part of URL address, which holds the name
<controller_path>. Individual methods of the controller
are mapped into the concrete part of URL address, called
<action>, at their @Action annotation names.
http://<server>/<app_root>/<controller_path>/<action>
In our example we want to map the controller HelloController
into the URL address:
http://localhost:8080/tutorial/hello-controller
The method helloWorld() can be then called by a HTTP request
at the URL address withs action name hello-world and the output
of the method will be shown on the web page.
http://localhost:8080/tutorial/hello-controller/hello-world
The mount method is called in initialize method of the
TutorialApplication.java class, which serves as
an application initializer.
After adding the mount method, the file TutorialApplication.java
now looks like this:
package tutorial;
import prest.core.Application;
import prest.core.ApplicationException;
import tutorial.controllers.HelloController;
public class TutorialApplication extends Application {
@Override
public void initialize() throws ApplicationException {
mount("/hello-controller", new HelloController());
}
}
After adding the HelloController class
the actual address structure now looks like this:
tutorial |-- docs |-- lib | `-- prest.jar |-- src | `-- tutorial | |-- TutorialApplication.java | `-- controllers | `-- HelloController.java |-- web | |-- META-INF | |-- WEB-INF | | `-- web.xml | `-- index.html |-- build.properties `-- build.xml
Ant
Now, the application is ready to be compiled and deployed into
servlet container. These actions can be executed by a command
ant install from the command prompt.
The application startup
After the successful compilation and installation the application runs
and we can test HelloController and his method
helloWorld() on URL address:
http://localhost:8080/tutorial/hello-controller/hello-world
pREST controller
In the previous example we have shown, how the simple controller works. In this section we will describe the individual characteristics and the ways of mapping the controller a specific URL address.
Output handling
The HTTP response body can contain either a text, or binary stream. If the controller's method returns one of the following types
java.lang.Stringjava.io.Readerbyte[]java.io.InputStream
or their successors then the object of this type is naturally transformed to the HTTP response body.
In general the method of the controller returns arbitrary Java object,
which does not have to represent a text or a binary stream. In that case
the string representation of an object is written into HTTP response body.
by calling toString() method.
String as a return type
Let's assume that we have the following URL address
http://localhost:8080/tutorial/output/string
We want the action string, which is mapped into
the controller method of the same name which itself is mapped into the part
of the URL address /output in our application, to
return in HTTP request body the string "Hello World".
We will create a new controller OutputHandlingController.
We will implement one public method string into the controller.
To each HTTP response is required to set ContentType
and in case of text response it is also required to set
CharacterEncoding into which the output data
should be transformed.
The controller instance of class OutputHandlingController
will be mapped on /output by calling the
mount("/output", new OutputHandlingController()) method
in the initialize() method body of the
TutorialApplication application class.
The class tutorial.controllers.OutputHandlingController
now looks like this:
package tutorial.controllers;
import prest.core.annotations.Action;
import prest.web.WebController;
public class OutputHandlingController extends WebController {
@Action(name="string")
public String string() {
setContentType(ContentType.TEXT_PLAIN_UTF8);
return "Hello world";
}
}
After the successful calling of the controller the
page on which the output of the string
method will be shown.
Hello world
Reader as a return type
We will implement the reader() method to the
controller class whose body looks like:
@Action(name="reader")
public Reader reader() {
setContentType(ContentType.TEXT_PLAIN_UTF8);
Reader reader = new StringReader("Hello. I'm reader.");
return reader;
}
The reader() method returns an object of Reader type,
which is written into HTTP response body.
After the successful calling of this action the
page on which the output of the string
method will be shown
Hello. I'm reader.
byte[] as a return type
At this example we are going to work with a binary data.
In the byteArray() method, which has been
implemented into controller and is the return type
byte[]. The body of the method looks like this:
@Action(name="byte-array")
public byte[] byteArray() {
setContentType(ContentType.TEXT_PLAIN_UTF8);
byte[] bs = "Hello. I'm your pREST :-)".getBytes();
return bs;
}
In this example we have created a byte[]
from the string with using a default encoding.
The string of the bytes, which was returned by the method, was
naturally transformed into a binary stream and it was written
to the HTTP body.
After the successful call of the action, the web page will show on which
the following output of the byteArray() method will be shown.
Hello. I'm your pREST :-)
IntputStream as a return type
Like in the previous example, we will create a byte[]
which will be naturally transformed into a binary stream.
@Action(name="stream")
public InputStream stream() {
setContentType(ContentType.TEXT_PLAIN_UTF8);
byte[] bs = "Hello. I'm your pREST :-)".getBytes();
InputStream stream = new ByteArrayInputStream(bs);
return stream;
}
After the successful call of the action of the controller,
the following web page will show on which
the output of the stream() method will be shown.
Hello. I'm your pREST :-)
Using of the toString method
In this example we are going to show the usage of toString()
method. We will create OutputObject class, which
have two variables of the String type :
value1 and value2. As the next thing, we will
overload (implement) toString() method, which will return
a string representation of this class, which is a string which contains
concatenated values of value1 and value2.
The OutputObject class now looks like this:
private class OutputObject {
private String value1;
private String value2;
public OutputObject(String value1, String value2) {
this.value1 = value1;
this.value2 = value2;
}
public String toString() {
return value1 + " " + value2;
}
}
The defined OutputObject class will be used as a
return type of the new controllers method with the Action name
object.
@Action(name="object")
public OutputObject object() {
OutputObject object = new OutputObject("text1", "text2");
return object;
}
After the successful call of the action, the following web page will show on which
the output of the object() method will be shown.
text1 text2
Input Handling
pREST resolves these types of input parameters:
-
URL parameters:
<url>/param1/param2/.../paramNParticular parameters are parts of URI. It is the URI part which is after the action and it forms a list of parameters with the "/" mark used as a separator.
Example:
http://server.net/company/depot/parts/p1/p2/p3
-
GET parameters:
<url>?key1=value1&key2=value2When the HTTP request is realized by a GET method, the particular parameters are added after the URI in urlencoded format. (
key1=value1&key2=value2...)Example:
http://server.net/company/depot/parts?k1=v1&k2=v2
-
POST parameters:
When an HTTP request is realized by a POST method, particular parameters are added into the the HTTP body in urlencoded format. (
key1=value1&key2=value2...)key1=value1&key2=value2
The web input can be transferred only in a string representation. The URL and its containing URL and GET parameters are strings as same as transferred POST parameters included in the HTTP body. One of the task which pREST resolves is mapping input parameters either request or URL into native Java types. pREST support following method's parameter types:
- Arbitrary types with string constructor and their fields
- Primitive types and their fields
- Special types
Arbitrary types with string constructor and their fields
During the HTTP (URL/GET/POST) parameters mapping into the controller's
method input arguments, string HTTP parameters must be converted into
the native Java objects. Conversion of string HTTP parameters into
Java type is realised by calling public string constructor,
whose argument is an appropriate parameter.
In case when constructor raises an exception the null
value is used instead of the object instance.
That means, if for example conversion from a string "a5"
into the object type Integer fails, the controller method
is called with an appropriate argument, whose value is null.
We resolve two types of mapping, if the mapping is either from URL or from request parameter
-
The parameters which has a public string constructor (they are not annotated by
prest.core.annotations.Keyannotation) are initialized from URL parameters in the order as they are listed in method declaration. In case of URL it is in a form:http://<server>/<app_root>/<controller_path>/<action>/<p1>/<p2>/.../<pN>
The parameters
p1, p2,...,pNare mapping into appropriate declaration arguments of method in this form:public void <actionMethod>(<type1> p1, <type2> p2,..., <typeN> pN) {}Let's have the following URL
http://localhost:8080/tutorial/input-handling/url-parameters1/value
Let's assume, that we have the
InputHandlerControllercontroller in the application. The controller is mapped into the URL part/input-handling. We want this method to be mapped onurlParameters1()method of this controller with a singleStringparameter.To
InputHandlerControllercontroller we will implement a publicurlParameters1()method to which an action of the same name is automatically mapped. The input parameter from URL is mapped into method's type parameterString. The classtutorial.controllers.InputHandlerControllerlooks like this:package tutorial.controllers; import prest.core.annotations.Action; import prest.web.WebController; public class InputHandlerController extends WebController { @Action(name="object") public String urlParameters1(String u1) { setContentType(ContentType.TEXT_PLAIN_UTF8); return "" + u1; } }The method
urlParameters1()has one (@Key unannotated) string parameteru1, so that during parameter mapping the framework attempts to create an instance of theStringtype by calling it's string constructor with the parametervaluesent as a URL parameter.In case that the URL address is in the following style:
http://localhost:8080/tutorial/input-handling/url-parameters1
it contains no URL parameter. then the instance of
Stringclass is not created (during constructor call, the exceptionjava.lang.NullPointerExceptionwas raised) and a methodurlParameters1()is called with a parameteru1, whose value isnull.The method parameter can be not only of a
Stringtype, but in arbitrary type which has a public string constructor.Let's think about this URL like this:
http://localhost:8080/tutorial/input-handling/url-parameters2/<p1>/<p2>/<p3>
We want this action to be mapped on
urlParameters2()method, which has 3 parameters: first two are standard Java typesIntegerandDouble, the last is self defined typeMyOwnType.public class MyOwnType { private String s; public MyOwnType(String s) { this.s = s; } public String toString() { return s; } }While all of the spoken types has a public string constructor, the method
urlParameters2()may looks like this:@Action(name="url-parameters2") public String urlParameters2(Integer i, Double d, MyOwnType myOwn) { setContentType(ContentType.TEXT_PLAIN_UTF8); if (i == null || d == null || myOwn == null) { return "Not all parameters was submitted or the conversion " + "to native types has not pass successfully"; } String result = ""; result += "1. URL parameter = " + i + "\n"; result += "2. URL parameter = " + d + "\n"; result += "3. URL parameter = " + myOwn; return result; }Let's assume a specific URL address:
http://localhost:8080/tutorial/input-handling/url-parameters2/123/999/abc
The values
123,999andabcare used in stages to build a construct object of typesInteger,DoubleandMyOwnType. After the successful call of a page will show, which contains the following output ofurlParameters2()method.1. URL parameter = 22 2. URL parameter = 33.3 3. URL parameter = abc
If some of the these parameters missed, the constructor of the specific parameter will be called with a
nullvalue. If the constructor call failed (constructor would raise an event) the specific method argumenturlParameters2()would have anullvalue. Mentioned properties are demonstrated in the following examples:Missing third URL parameter:
http://localhost:8080/tutorial/input-handling/url-parameters2/123/999
The constructor for the third argument of the method will be called with a
nullvalue, because among URL parameters the third parameter is missing. Because constructor of the typeMyOwnTypeaccepts the valuenullas a parameter, the instance of this type is correctly created.1. URL parameter = 22 2. URL parameter = 33.3 3. URL parameter = null
The wrong value of the parameter:
http://localhost:8080/tutorial/input-handling/url-parameters2/123/xyz/abc
The second argument of the method will be
null, because the construction of the object typeDoublefrom thexyzstring will not pass successfully.Not all parameters was submitted or the conversion to native types has not pass successfully
The empty list of URL parameters:
http://localhost:8080/tutorial/input-handling/url-parameters2
The all 3 parameters will have a
nullvalue.Not all parameters was submitted or the conversion to native types has not pass successfully
-
Parameters, which have a public string constructor and they are annotated by a
prest.core.annotations.Keyannotation are mapped from request (POST/GET) parameters by a specified key listed as an annotation parameter@Key("name")). In case of submitted form, the key is a request parameter value listed in an attributenameof the form input (input, select, textarea, ...).Let's assume HTTP request method GET on an URL in the following form:
http://<server>/<app_root>/<controller_path>/<action>?<k1>=<value1>&<k2>=<value2>&...&<kN>=<valueN>
Parameter with the keys
<k1>, <k2>,...,<kN>are mapped into the appropriate method declaration arguments in the following form:public String <actionMethod>(@Key("<k1>") <type1> v1, @Key("<k2>") <type2> v1,..., @Key("<kN>") <typeN> v3) {}Let's assume that we want the action name
request-parameters1corresponding to methodrequestParameters1()to handle the following form:<form method="get" action="request-parameters1"> <input type="text" name="name"/> <input type="text" name="age"/> <input type="text" name="kontakt"/> <input type="submit" value="Send"/> </form>
The method
requestParameters1(), which handles mentioned form can be defined like this:@Action(name="request-parameters1") public String requestParameters1(@Key("name") String s, @Key("age") Integer i, @Key("kontakt") MyOwnType myOwn) { setContentType(ContentType.TEXT_PLAIN_UTF8); if (s == null || i == null || myOwn == null) { return "Not all parameters was submitted or" + "the conversion to native types has not pass successfully"; } String result = ""; result += "name = " + s + "\n"; result += "count = " + i + "\n"; result += "kontakt = " + myOwn + "\n"; return result; }Request parameter mapping into method
requestParameters1()overshoots by appropriate keys, which are values of the attributenameof the form input. They are listed as annotation parameters:@Key("name"),@Key("age")and@Key("contact").As same as in the URL parameter mapping, in this case also the object construction could not pass successfully and the method gets in the appropriate argument a
nullvalue.In the case, that for the appropriate key there is more then one value (as for example if in the form exists more then one entry with the same name)
http://<server>/<app_root>/<controller_path>/<action>/<?k1=value1&k1=value2;
they can be mapped in the way that the method input parameter is an array.
@Action(name="request-parameters2") public String requestParameters2(@Key("name") String[] strings, @Key("age") Integer[] integers, @Key("kontakt") MyOwnType[] myOwns) { setContentType(ContentType.TEXT_PLAIN_UTF8); String result = ""; for (int i = 0; i < strings.length; i++) { result += "name[" + i + "] = " + strings[i] + "\n"; } for (int i = 0; i < integers.length; i++) { result += "count[" + i + "] = " + integers[i] + "\n"; } for (int i = 0; i < myOwns.length; i++) { result += "count[" + i + "] = " + myOwns[i] + "\n"; } return result; }This action for the URL address
http://localhost:8080/tutorial/input/request-parameters2?name=John&name=ABC&age=55
returns:
name[0] = John name[1] = ABC age[0] = 22
Primitive types and its arrays
The primitive types mapping is the same as string constructor type mapping with a difference that in the case of primitive types the object type is transferred into its primitive equivalent type. If the object equivalent instance of primitive Java type could not be created, an error will occur before the method calling.
It is not possible to handle primitive Java type conversion errors inside controller's method body. Use primitive Java types as controller's method arguments carefully.
Build in aggregated types
There are some situations, when the number of the input parameters is variable. In that case we cannot use direct mapping of single characters For this reason we use aggregated types:
-
prest.core.types.UrlParameters- includes every URL parameters -
prest.core.types.RequestParameters- includes every request parameters -
prest.core.types.FileRequestParameters- includes every sent files
User defined aggregated types
pREST enables to create user defined types, which instances are automatically created and initialized by appropriate aggregated types in case of using the user defined types as a controller method types. The condition is that the user defined type implements (just) one of the following interfaces:
-
prest.core.types.UrlParametersTypeLet's assume, that we have an action, which shows a name and an age of some person, while these data are sent as last but one parameter and last parameter. In this case when we do not know the number of every parameter we cannot use the classic URL parameter mapping. One solution for this situation can be a usage of
prest.core.types.UrlParametersinterface.package prest.core.types; public interface UrlParametersType { int getSize(); void initialize(UrlParameters parameters) throws Exception; }This interface defines two methods which must be implemented. The method
initialize()with an argument ofprest.core.types.UrlParameterstype. This class includes all necessary methods which are needed for working with URL parameters In our case we need especially thegetSize()method, which returns a number of all URL parameters, and also theget(int index)method, which returns the actual URL parameter by providing an index of URL parameter.tutorial.controllers.UrlPersonlooks like this:import prest.core.types.UrlParameters; import prest.core.types.UrlParametersType; public class UrlPerson implements UrlParametersType{ private String name; private Integer age; public int getSize() { return 0; } public void initialize(UrlParameters urlParameters) throws Exception { int count = urlParameters.getSize(); if (count < 2) { return; } try { name = urlParameters.get(count - 2); age = Integer.parseInt(urlParameters.get(count - 1)); } catch (NumberFormatException e) { ; } } public String toString() { return "Name: " + name + "\n" + "Age: " + age; } }This class can be now used as an argument of some controller's method. Let's name it
userDefinedParameters1()@Action(name="user-defined-parameters1") public String userDefinedParameters1(UrlPerson person) { setContentType(ContentType.TEXT_PLAIN_UTF8); return person.toString(); }The output of this action by using this url:
http://localhost:8080/tutorial/input/user-defined-parameters1/a/b/c/John/22
is
Name: John Age: 22
Despite of the number of parameters in the URL address, this action always takes the last two of them.
-
prest.core.types.RequestsParametersTypeThis interface is like the previous one. A class which implements the interface must define
initialize()method, which has a parameter ofprest.core.types.RequestParameterstype and a methodgetKeys(), which returns a set of all expected or actual keys.As same as in the previous example let's assume that an action must work with a name and with an age of the person while these two parameters are not got in the URL parameter form, but in the request parameter form with keys
nameandage. The classtutorial.controllers.RequestPersonwill implementprest.core.types.RequestsParametersTypeinterface and ininitialize()method, which needs to be defined, will get and object ofprest.core.types.RequestParameterstype as a parameter. The object type allows method to get access to all request parameters.package tutorial.controllers; import java.util.Set; import prest.core.types.RequestParameters; import prest.core.types.RequestParametersType; public class RequestPerson implements RequestParametersType{ private String name; private Integer age; public Set<String> getKeys() { return null; } public void initialize(RequestParameters requestParameters) throws Exception { try { name = requestParameters.get("name").get(0); age = Integer.parseInt(requestParameters.get("age").get(0)); } catch (Exception e) { ; } } public String toString() { return "Name: " + name + "\n" + "Age: " + age; } }A new method with the argument of
tutorial.controllers.RequestPersontype will be added into the controller.@Action(name="user-defined-parameters2") public String userDefinedParameters2(RequestPerson person) { return person.toString(); } -
prest.core.types.FileRequestsParametersTypeLike in the previous examples, this interface declares a
initialize()method with a parameter ofprest.core.types.FileRequestParameterstype.The usage of this type we will demonstrate on the next example. We want the action method
userDefinedParameters3(), to show the informations about uploaded files.@Action(name="user-defined-parameters3") public String userDefinedParameters3(FileRequestParameters fileReqParams) { setContentType(ContentType.TEXT_PLAIN_UTF8); String result = ""; result += "<html>" + "<head><title></title></head>" + "<body>"; result += "<form method=\"post\" action=\"user-defined-parameters3\"" + "enctype=\"multipart/form-data\">" + BR + "subor1<input type=\"file\" name=\"subor1\" />" + BR + "subor1<input type=\"file\" name=\"subor1\" />" + BR + "subor2<input type=\"file\" name=\"subor2\" />" + BR + "<input type=\"submit\" name=\"submit\" value=\"send file\" />" + BR + "</form>"; Set<String> keys = fileReqParams.getKeys(); for (String key : keys) { result += "<h2>" + key + "</h2>"; java.util.List<FileDetail> fileDetails = fileReqParams.get(key); for (FileDetail fileDetail : fileDetails) { result += "fileName = " + fileDetail.getFileName() + BR + "contentType = " + fileDetail.getContentType() + BR + "size = " + fileDetail.getSize() + BR + BR; } } result += "</body></html>" return result; } -
prest.core.types.InputTypeis the most general among all four types. Its method
initialize()expects parameters ofprest.core.io.Requesttype, which encapsulates all three previous types.Let's assume a situation, that an action should work with some complex number in the way that its real part is got by a first URL parameter and the imagine part is got among request parameters under the "imaginary_part" key. Because we need the access not only to URL parameters but also to request parameters we will create
tutorial.controllers.ComplexNumberclass, which implementsprest.core.types.InputTypeinterface.package tutorial.controllers; import prest.core.types.InputType; public class ComplexNumber implements InputType { Double realPart; Double imaginaryPart; public void initialize(prest.core.io.Request request) throws Exception { try { realPart = Double.parseDouble(request.getUrlParameters().get(0)); imaginaryPart = Double.parseDouble(request.getRequestParameters() .get("imaginary_part").get(0)); } catch (Exception e) { ; } } public String toString() { return "[" + realPart + "; " + imaginaryPart + "]"; } }This type can now be used as argument of
userDefinedParameters4method argument.@Action(name="user-defined-parameters4") public String userDefinedParameters4(ComplexNumber number) { return number.toString(); }The output for this URL
http://localhost:8080/tutorial/input/user-defined-parameters4/123?imaginary_part=321
is
[123.0; 321.0]
Annotations
pREST introduces couple types of annotations. We can divide them into two parts:
-
Those which represents meta data for controllers,
methods and attributes:
Doc,KeyaAction. - Those, which serves for filter ordering for method calling.
Action annotation
Action annotation is used for mapping action name (part of URL) and HTTP method into the pREST controller Java method. If the action name is not specified or is empty string, Annotated Java method is mapped directly into the controller path.
@Action(name = "product", httpMethod = "DELETE")
public void deleteProduct(int id) {}
In the shown example, deleteProduct method
is mapped into the URL part product, while the
method is called, only when the HTTP request is sent by HTTP method
DELETE.
Doc annotation
Doc annotation serves for documenting the controller, its methods and parameters. It has two parameters:
-
value- is a document string used for a class description or a parameter description. -
publish- is an optional parameter. It can be used in the case of a class annotation when we want to disable publishing the documentation.
Every pREST controller provides a build-in interface for documentation and also it provides self and parameter test.
-
_doc_provides controller documentation, its method and parameters -
_docs_provides the full list of controllers and their documentations -
_test_serves as a testing interface for testing the services which are provided by the controller
Let's show how can we by the prest.core.annotations.Doc
annotation add a documentation to:
-
class
tutorial.controllers.AnnotationsController -
method
methodDoc()of the controllertutorial.controllers.AnnotationsController -
parameter
s1of the methodmethodDoc()
package tutorial.controllers;
import prest.core.annotations.Action;
import prest.web.WebController;
@Doc(value="Documentation for class AnnotationsController")
public class AnnotationsController extends WebController{
@Action
public String defaultAction() {
setContentType(ContentType.TEXT_PLAIN_UTF8);
return "Output from method 'default_action()'";
}
@Action(name = "method-doc")
@Doc("Method with method documentation " +
"and without method parameter documentation")
public String methodDoc(String s) {
setContentType(ContentType.TEXT_PLAIN_UTF8);
return "with documentation";
}
@Action(name = "method-and-param-doc")
@Doc("Method with method documentation " +
"and also with method parameter documentation")
public String methodAndParamDoc(
@Doc("Documentation for method parameter") String s1,
String s2)
{
setContentType(ContentType.TEXT_PLAIN_UTF8);
return "with parameter documentation";
}
}
The interface for tutorial.controllers.AnnotationsController
class can be found on the following URL address:
http://localhost:8080/tutorial/annotations/_doc_
Also in the addresses
http://localhost:8080/tutorial/annotations/_docs_
http://localhost:8080/tutorial/annotations/_test_/<action>
we have a a graphical interface available for every application controllers and for testing their methods.
Filters
In pREST framework the filters are used for input and output modification. It separates the input and output handling from methods, which executes some jobs and which can return some output. Then filter can modify this output (e.g serialize) and push further. Filter to method assignment is realized by annotations. This approach is characteristic for aspect oriented programming.
Filter rules:
- Filters are pushed to filter chain in the order how their annotations are listed.
- Annotations listed before the controller class specifies IO filters for any controller's method.
- Annotations listed before the controller's method specifies IO filters just for the annotated method.
@Filter1
public class FiltersOrder {
@Action(name="method1")
public String method1() {
setContentType(ContentType.TEXT_PLAIN_UTF8);
return "abc";
}
@Action(name="method2")
@Filter2
@Filter3(param = "value1")
@Filter4
public String method2() {
setContentType(ContentType.TEXT_PLAIN_UTF8);
return "xyz";
}
}
In this case before method1() is executed
the filter Filter1 is applied.
Because it is assigned to the class, it is applied before
any method of the class.
During handling of the method method2()
the other filters are applied in this order:
Filter1, Filter2,
Filter3 and Filter4.
Method io filter is an implicit pREST filter, dedicated to controller's method excution and method input/output handling.
Practical examples of the filter usage
Example 1 - duration IO filter
Let's assume, that we want to log the method execution duration of the
durationTest() method of
tutorial.controllers.FilterController controller.
package tutorial.controllers;
public class FilterController extends WebController{
@Action(name="duration-test")
public String durationTest() {
setContentType(ContentType.TEXT_PLAIN_UTF8);
return "Duration";
}
}
which is mounted into URL:
http://localhost:8080/tutorial/filters/
Let us create IO filter for measure action execution time. We have to create:
-
tutorial.annotations.DurationpREST filter annotation -
Annotation handler in the same package as pREST filter annotation
with name of annotation with suffix "Filter"
tutorial.annotations.DurationFilter
First we create an annotation tutorial.annotations.Duration,
through which we asigne filter to the method.
package tutorial.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = {ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Duration {
}
The annotation @Duration itself is annotated by another
two annotations:
-
java.lang.annotation.Target- it is a meta annotation which defines to which elements (clases, methods, parameters ...) can annotation be applied. -
java.lang.annotation.Retention- is a meta annotation which defines the amount of validity of the annotation, it has to have aRetentionPolicy.RUNTIMEvalue
Second, we implement pREST annotation IO filter handler in the same
package as pREST IO filter annotation @Duration with name
of annotation with suffix "Filter" -
tutorial.annotations.DurationFilter.
This class has to implement prest.core.filters.Filter
interface.
package tutorial.annotations;
import java.lang.annotation.Annotation;
import prest.core.Application;
import prest.core.filters.Filter;
import prest.core.filters.FilterChain;
import prest.core.filters.FilterException;
import prest.core.io.Environment;
public class DurationFilter implements Filter {
public void execute(Environment environment, FilterChain chain)
throws FilterException
{
long start = System.currentTimeMillis();
// call next IO filter in filter chain
chain.doFilter(environment);
long duration = System.currentTimeMillis() - start;
Application.logger().debug("Duration: %d ms", duration);
}
public void setAnnotation(Annotation annotation)
throws FilterException {
}
}
The class contains two methods: execute() and
setAnnotation(). Their purpose is:
- execute(Enviroment enviroment, FilterChain chain)
-
The method serves as IO filter functionality when
it comes into queue in filter chain.
It has two parameters:
-
enviroment- is input/output context -
chain- is an object which represents the filter chain and it contains adoFiltermethod, which initiates the filter functionality of the next filter in filter chain.
-
- setAnnotation(Annotation annotation)
-
Its goal is to provide access to IO filter's annotation and
annotation parameters for IO filter annotation handler.
The method has only one parameter:
-
annotation- is an object which represents an annotation.
-
In our example we have implemented a method execute()
in this way: with method System.currentTimeMillis
we have counted the time of executing chain.doFilter and this
time was logged in the debug level by a method
Application.logger().debug.
The @Duration annotation can be use to
assigne to any pREST controller's method now.
So we can assigne execution time measiring filter to the
duration() method of the
FilterController controller.
@Action(name="duration-test")
@Duration
public String durationTest() {
setContentType(ContentType.TEXT_PLAIN_UTF8);
return "Duration";
}
The result of the action's calling can be found on the URL address:
http://localhost:8080/tutorial/filters/duration-test
and also in the action's log, where should we found something like this:
Sep 30, 2008 12:02:36 PM org.apache.catalina.core.ApplicationContext log INFO: DEBUG pREST 32: Duration: 2 ms
Example 2 - uppercase IO filter
In the next example we will create a filter, which test, whether
the input data are of the type String and then
convert the letters into uppercase or lowercase acording IO filter
annotation.
Let's create an annotation class with the name UpperLower:
package tutorial.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE, ElementType.METHOD})
public @interface UpperLower {
String value() default "lower";
}
Through String value() default="lower"
we will define the annotation parameter and its implicit value.
The filter implementation class
tutorial.annotations.UpperLowerFilter
looks like this:
package tutorial.annotations;
import java.lang.annotation.Annotation;
import prest.core.filters.Filter;
import prest.core.filters.FilterChain;
import prest.core.filters.FilterException;
import prest.core.io.Environment;
public class UpperLowerFilter implements Filter {
boolean upper = true;
public void execute(Environment environment, FilterChain chain)
throws FilterException
{
chain.doFilter(environment);
Object out = environment.getResponse().getOutputData();
try {
if (out instanceof String) {
String s = (String) out;
if (upper) {
s = s.toUpperCase();
} else {
s = s.toLowerCase();
}
environment.getResponse().setOutputData(s);
}
} catch (NullPointerException e) {
Application.logger().debug("UpperLower: Output is null", e);
} catch (Exception e) {
Application.logger().debug("UpperLower: ", e);
}
}
public void setAnnotation(Annotation annotation) throws FilterException {
UpperLower a = (UpperLower) annotation;
if (a.value().equals("lower")) {
this.upper = false;
}
}
}
In the method setAnnotation we will get access to the
annotation parameter and then we can set the value of the private variable
according to annotation parameter value. The annotation parameter
define whether the text will be transformed into capitals or into lowercase.
The method execute() controls, whether
the returned data are String types, or not.
If everything is in order, the variable upper is evaluated
according to this value is either called the toUpperCase method,
or the method toLowerCase of the String class.
We can annotate upperLowerTest() method of the
FilterController controller with
@UpperLower annotation now.
In annotation we can set the parameter value to
"upper" or "lower" to choose output
string IO filter algorithm.
The annotated pREST action method looks like this:
@Action(name="upper-lower-test")
@UpperLower(value="upper")
public String upperLowerTest(String text) {
setContentType(ContentType.TEXT_PLAIN_UTF8);
return text;
}
We hope this tutorial will help you to build better, faster and safer Web applications and REST web services.