Project Phobos is a lightweight, scripting-friendly, web application
environment running
on the JavaTM platform. With Phobos,
developers can write web applications using any number of
scripting languages. In doing so, they can take advantage of a very
interactive development
environment that does away with explicit compilation and deployment
steps, all while
retaining the power of the Java platform.
This document introduces some basic features of the Phobos architecture that are of particular interest to application developers. A complementary document, the introductory tutorial, shows how all these elements come together in a simple application. You might find it useful to refer to the tutorial while reading this document. We plan on publishing additional documents focusing on more advanced topics in the coming weeks.
Since we currently use JavaScript as the main scripting language in Phobos, this document assumes basic knowledge of the JavaScript language.
Architectural Overview
Platforms and Environments
Resources and Directory Layout
Scripts
The JavaScript Engine
The Embedded JavaScript Engine
Views
Paths
Controllers
Predefined Global Variables
Life cycle Events
Sessions
Libraries
The following diagram shows the high-level components of Phobos.

The parts in blue/green represent the infrastructure on top of which Phobos proper runs. As the next section makes clear, Phobos applications can be deployed on different runtime environments. Typically, you will deploy your Phobos application as regular web application in any compatible servlet container.
The parts in red are the main components of Phobos itself. They include the following:
Finally, the parts in yellow represent elements of an application. These include:
The following sections explain in more detail each of the concepts that appear in the diagram, plus other elements that we couldn't fit in a single picture but that are important to Phobos developers.
Phobos can run on different platforms. Currently, we support two of them:
The next section details how the platform on which Phobos is running affects the physical layout of an application. For example, it is possible that certain external resources (such as databases) are available only when Phobos is running on a particular platform. Applications running on an application server like GlassFish should use the networked database that ships as part of the server itself rather than using an embedded database instance.
Code that depends on a particular platform
can use the globals.platform variable
to discover the platform in use. The values corresponding to the
three
platforms are as follows:
In addition to platforms, Phobos defines the notion of an environment.
An environment is a logical configuration for an application.
Examples of
logical configurations are development, testing, and production. During
its lifetime,
an application will be deployed in different environments, in
particular as it
progresses from the development stage to the production stage.
Environments help make these
transitions painless by capturing all the information specific to each
of these
stages. For instance, developers might want to avoid enabling certain
performance
optimizations at development time (perhaps because they adversely
affect the
development cycle) and enable them only during system testing and in
production instead.
Phobos expects developers to write applications with one or more
environments
in mind. The person responsible for deploying an application can
then select
the environment it wants to use by using platform-specific means, such
as by using system properties.
A running application can find out what environment it is running under
by
inspecting the globals.environment
variable. The default environment is "development".
In the context of Phobos, a resource is any file that is accessible to an application. This includes all the scripts and static content (such as HTML pages) that are part of an application. It also includes all the scripts that compose the Phobos frameworks.
The resources of a Phobos application are organized according to a
specific directory layout.
Phobos distinguishes between the virtual directory
layout and the physical one.
The virtual directory layout is identical across all platforms, whereas
the physical directory layout varies with the platform.
In a virtual layout, an application has four directories at the top level:
The reason for keeping static content separate is that it's often possible to use platform-specific mechanisms to serve static content with much higher-performance than any dynamic (i.e. script-generated) one. Segregating static content in its own directory simplifies this task. Environment information is separate from application information to make it easier to share these files across multiple applications.
Inside application code, you always use virtual names to
refer to files. So, regardless of the
platform in use, you can refer to a static file called index.html
by using the name
/static/index.html. Note also that virtual names are always
absolute, meaning that they start with
a slash character. In the future, Phobos might allow URI
schemes to appear at the front
of a virtual name, thereby permitting references to remote resources.
An example could be scripts, templates, or documents stored in a
database
or generated on the fly by some external application.
Depending on the platform in use, the physical layout of an application might change. The following sections describe the physical layout in each of the platforms referenced in section Platforms and Environments.
When running as a standard web application, the application,
environment and framework
virtual directories are mapped to directories with the same name under
the WEB-INF directory.
All static content, which is in the static virtual directory,
is mapped to the
top-level directory of the web application. Code Example 2 shows
the physical layout of an application running in a web application.
myApplication/
...static files...
WEB-INF/
application
environment
framework
In this platform, used by the IDE when debugging an application, only one application can run at any given time, and so the physical layout is the same as the virtual one, meaning that no sharing or remapping occurs. The only exception is that the framework directory will most often come from a shared Phobos installation rather than being part of the application.
application
environment
framework (* may not be present)
static
The easiest way to write application code to serve an HTTP request in Phobos is to put it in a script.
A "hello, world" script in JavaScript would look like this:
response.setStatus(200);
response.setContentType("text/html");
writer = response.getWriter();
writer.println("Hello from Javascript!");
writer.flush();
The application/script directory is preconfigured to contain scripts, but you can override this setting by manipulating paths as described in Paths. Incidentally, the special URL, / is mapped to a script called index.js.
The request and response global variables are bound to the request and response objects respectively. Although the actual class of the objects depends on the platform in use, in most cases these objects will conform to the contract for the javax.servlet.http.HttpServletRequest and javax.servlet.http.HttpServletResponse classes, with the exception of any servlet container-specific methods. Note that sessions are handled differently in Phobos than in a regular servlet container. See the Sessions section for more details.
Phobos uses JSR-223 to invoke any scripts. As a consequence, scripts can be written in any language as long as there is a JSR-223-compliant scripting engine for it in the classpath. Phobos comes with two built-in scripting engines:
At this stage, JavaScript is the language that we recommend for most application code. Phobos itself is written mostly in JavaScript, and the framework assumes that the language you use is capable of calling into JavaScript. At this time, the task of calling into JavaScript from another scripting language is a complicated one, requiring knowledge of the internals of the Mozilla Rhino scripting engine. Of course, it's possible to define a first-class interface to JavaScript in other scripting languages (much like the Java interfaces they already provide) in order to get easy access to Phobos libraries as a byproduct of that interface.
The JavaScript engine in Phobos is based on the one in the Java Platform, Standard Edition
6
(Java SE 6), code-named Mustang, and it uses Mozilla
Rhino for its
operations. Compared to the JavaScript engine in Mustang,
the one in Phobos has these additional capabilities:
E4X allows the use of XML literals and XPath-like expressions inside JavaScript code. For example, the "hello, world" example in Code Example 5 can be rewritten as follows. Note the lack of double quotes around the HTML text in the first line of code.
var text = <html><head><title>Hello</title></head><body>Hello from Javascript!</body></html>;
response.setStatus(200);
response.setContentType("text/html");
writer = response.getWriter();
writer.println(text);
writer.flush();
The embedded JavaScript engine allows you to embed JavaScript statements and expressions inside an HTML file, an XML file, or a text file, much like PHP does.
The extension that identifies Embedded JavaScript files is .ejs. The body of an .ejs file is copied verbatim to the standard writer. In most scripting languages, you can obtain this writer (defined by JSR-223) by evaluating context.writer.
Statements and expressions surrounded by special markers, on the
other hand, are evaluated
at runtime. This facility is analogous to scriplets in JSP.
Code Example 7 shows the syntax of an expression.
Code Example 9 shows the syntax of a statement.
<%= ... a JavaScript expression ... %>
Two and two is <%= 2 + 2 %>
<% ... JavaScript statements ... %>
Two and two <% var a = "silent assignment" %> is
<% context.getWriter().print(2 + 2) %>
Although .ejs files are scripts in their own right, they
are especially well-suited to
defining the views of an application. The following
section explains views, as
defined by Phobos.
It is good practice to separate the code that processes a request from that which renders a page to be sent back to the browser. The job of scripts and controllers is to process a request; rendering a page is the domain of the view.
The following script makes a simple demonstration of separating a
page request for the rendering of that page. The path to the page
is /application/view/status.ejs. The script itself
might be located in /application/static/status.js.
model = { message: "everything is fine" };
library.view.render("/application/view/status.ejs");
<html>
<head>
<title>Status</title>
</head>
<body>
<%= model.message %>
</body>
</html>
If you compare this script and view with a straightforward script from the Scripts section, you'll see that the code to set the status code and content type for the response is gone. This task is performed automatically by the library.view.render function.
It would be tedious to write a long string like "/application/view/status.ejs" every time you want to refer to a given view. As a shorthand, Phobos defines several paths to commonly used resources. Library functions that accept a resource name as a parameter will resolve any relative names (those not starting with a slash character) using the path appropriate to the resource they are trying to resolve.
For instance, the library.view.render function takes the name of a view resource as an argument. Therefore, Code Example 11 can be rewritten as follows:
model = { message: "everything is fine" };
library.view.render("status.ejs")
render function will try to locate a resource called status.ejs
on the path for view resources,
which includes /application/view. As a result, the
render function resolves to /application/view/status.ejs.
All predefined paths live inside the application.path object. There are paths for controllers, libraries, modules, scripts and views. The corresponding variables are:
application.path.controller
application.path.library
application.path.module
application.path.script
application.path.view
A path is simply a list of strings. Application code can change or extend predefined paths by using the usual array functions. Since this changes the configuration of the application, it is typically done at startup time. For instance, to add the application/myscripts virtual directory to the script path, you can write:
application.path.script.push("/application/myscripts");
Because scripts are mapped to URLs that contain the script name, the scripts themselves cannot be encapsulated like an object. Furthermore, the task of creating lots of different scripts to handle multiple related actions, like displaying and updating data on a form can become tedious. For this reason, Phobos defines controllers.
A controller is an instance of a controller class located in a
sub-namespace of the controller
namespace. The linking between request URLs and controllers is done by
naming convention, but is customizable
by the developer. For example, if the Phobos framework receives a
request for the following URL, /form/update, it looks for
a controller class named Form
in the form namespace
of the controller hierarchy.
/form/update
Upon finding this class, the framework instantiates it and looks for an update method. If it finds one, it invokes it.
JavaScript supports a prototype-based object system but no built-in
notion of a packaging namespace. To ensure that the controller
mechanism works well with JavaScript, the Phobos framework introduces
some special naming conventions and library functions. To
illustrate, let's take a more in-depth look at how Phobos invokes the
form controller's update method:
The following code shows the piece of the form.js file that shows how to define a constructor for a controller called form.
library.common.define(controller, "form", function() {
this.Form = function() {
this.update = function() {
// ... code to update the form goes here ...
}
}
});
The library.common.define library function is used to
define a namespace as a
child of another. In this case, it is defining form
as a child namespace of controller. The properties
assigned to the this
object inside the body of the library.common.define are the
externally visible members
of the namespace. The anonymous function that
appears as the third element
in the call to define is necessary to delay evaluation of its
body to a later time so that
define can avoid uselessly redefining a namespace multiple
times.
It is worth pointing out that this is bound to different objects at different times:
Phobos enforces a convention that class names are uppercase. Technically, a class name is really the name of a function that is used as a constructor. That is why the second line of code in Code Example 17 must be written the way it is.
Because of the extreme malleability of the JavaScript object model,
there are other ways to obtain the
same result. Code examples 18 and 19 are two other ways to do what code
example 17 does.
library.common.define(controller, "form", function() {
this.Form = function() {};
this.Form.prototype.update = function() {
// ... code to update the form goes here ...
}
}
});
library.common.define(controller, "form", function() {
this.Form = function() {};
with(this.Form) {
prototype.update = function() {
// ... code to update the form goes here ...
}
}
});
A future version of Phobos might introduce some syntactic sugar to make namespace and class definitions simpler. In this case, we'll likely create a new scripting language for a JavaScript dialect extended with keywords such as package and class.
Given the form controller, what happens if a client tries to access a URL like /form/foo, for which there is no action method? In this case, the framework will then look for a method called onRequest, which acts as a catch-all action for this controller class. If that method is not present, the framework will return a 404 NOT FOUND error to the client.
Until now, we haven't explained how expressions like library.view.render work. Phobos defines a number of global variables that are the root of distinct namespaces. In addition to library and controller, there is also a module global variable. Each namespace has a corresponding path, as explained in Paths.
Modules are similar to libraries, but they are intended exclusively
for application use. As a guideline, you should
define your own library (such as application/library/mylibrary.js)
only if you plan on adding that code to
the framework virtual directory at some point.
Moving a
library to the framework is completely transparent
because the default path for libraries includes both /framework/library
and /application/library directories. As a result you
can freely move a file between them.
Code that is not meant to become part of the framework should be defined in a module. For example, you might want to have a "helpers" module containing useful functions that you plan to invoke from multiple controllers in your application. You can accomplish this by defining a file called application/module/helpers.js of this form:
library.common.define(module, "helpers", function() {
this.myUsefulFunction = function() {
// ... useful code goes here ...
}
});
module.helpers.myUsefulFunction( ... )
Just as for controllers, multiple
equivalent styles of module definitions are possible, as shown in the
following code.
library.common.define(module, "helpers", function() {
function myUsefulFunction() {
// ... useful code goes here ...
}
// export the function
this.myUsefulFunction = myUsefulFunction;
});
In addition to the variable associated with namespaces and the request and response variables described earlier in Scripts, Phobos defines some additional global variables as follows:
invocation.myDatum = 5;
Options, such as paths, used to configure an application are located under application.options. Another commonly used option is application.options.datasource, which indicates the datasource that database access libraries use by default.
Phobos applications have a simple life cycle. The two most important events are startup and shutdown. Except for background tasks, which will be described in a future document, all a Phobos application does in between startup and shutdown is to serve incoming requests.
You can have application code that is executed when startup or shutdown occurs. At startup, Phobos attempts to run a number of scripts and functions in a fixed order. Let's assume that the platform Phobos is running on is identified by the string "PLATFORM" and the environment by the string "ENVIRONMENT". Then the scripts and functions executed at startup are the following:
The order is established according to the expected behavior of each
script or function as described in the following list.
At shutdown, the following steps are taken:
Phobos manages sessions using cookies.
A session is represented by an object containing some name/value properties. You can access this object using invocation.session. Don't get misled by the fact that it lives under invocation: Although the object itself is recreated at each invocation, its contents will persist. (For the curious, session data is currently stored under application.sessionStore. This might change in the future, perhaps when we add file- or database- session persistence.)
You can write, read, and delete session-scoped properties using the usual JavaScript syntax, as shown in the following code:
invocation.session.userName = "Fred";
// ... some code here ...
model = { name: invocation.session.userName };
// ... more code here ...
delete invocation.session.userName;
The predefined property invocation.session.id returns a string that uniquely identifies the session. One use for this is as as a database key. A property whose name starts with __ (double underscore) are reserved for framework use.
An application can disable session management to avoid the
performance
overhead that goes with it by setting the following property in a
startup
script.
application.options.session.enabled = false;
First introduced by Ruby on Rails, the flash is a specially-scoped property bag. Its lifetime is exactly two request-response cycles: Data inserted in the flash during processing of one request will be visible during the next one and then automatically collected. The flash is useful when you want to pass information from one action to the following one, such as after a redirect.
In Phobos, you access the flash with invocation.flash. In reality, the flash is part of the session state. Therefore, you need to be careful to avoid using the same property name for a session property and a flash property.
invocation.flash.message = "Login failed";
library.httpserver.sendFound("... redisplay the login page ...");
// then in the action we redirected to:
model = { message: invocation.flash.message };
library.view.render("... some view ...");
Phobos comes with a growing number of JavaScript libraries,
accessible under the library namespace.
The most commonly used libraries are the following:
At this time, there is no JavaScript equivalent of the JavadocTM tool, but we hope to get one soon enough. Until then, the only way to see what functions are defined in a particular library is to browse its source code under framework/library/LIBRARYNAME.js.
Currently, Phobos does not add any properties to any predefined JavaScript objects. There is a well-defined place to add these extensions though. Based on initial user feedback, we plan to add more convenience functions there, much like the Prototype JavaScript library does.
Finally, since JavaScript and most other scripting languages running on the Java platform have an excellent interface to Java APIs, scripts can easily call any Java libraries. A standard distribution of Phobos includes the JDOM and ROME utility libraries:
When using a Java class library from JavaScript, you can simply
refer to a
qualified class name using the Packages
global variable. For example, you can create a JDOM SAXBuilder
object using the following code.
var builder = new Packages.org.jdom.input.SAXBuilder();
We hope this document helped you grasp the most important concepts
in Phobos. If you haven't done it yet,
work through the tutorial and try writing
some simple web applications with Phobos.
Follow-up documents will detail more advanced functionality like AJAX (using technologies like jMaki), persistence, RSS/Atom, customizing the URLs used by your application and much more. Since Phobos in work in progress, we invite you to join the mailing lists on the Phobos project on java.net and contribute to its development.