Resource Controller

Version 14

    h2. Status

     

    WIP see:

     

    * video showing the declaration of portlet resources : [http://vimeo.com/33988500]

     

    h2. Description

     

    This specification describe how to improve the current resource serving of GateIn by integrating it with the navigation controller. Resources become routable by the portal and the configuration will be operated to the +controller.xml+ file

     

    * Resource serving URL are not hard coded anymore

    * Resource serving becomes easily configurable

    * Resource serving is easy to version

     

    It also implies that any resource is now served by the portal war file instead of the application containing the war file, allowing the portal to easily minify and compress the resource. It will allow to simplify deployment of application, removing the need for the +ResourceRequestFilter+ to be added to deployed war files.

     

    For the moment we will focus on javascript resource serving but soon we will also work on css resource serving. Therefore at the moment we only describe the modification operated to make resource serving integrated with the controller.

     

    h2. Specification

     

    h3. Resource

     

    A resource is identified for a script (and later a stylesheet too) composed of

     

    * A scope: the scope has a functional meaning for the portal but not for the resource server. At the moment we have identified three scopes: +SHARED+ a resource that does not depend upon a particular portal entity, +PORTAL+ a resource that is in relation with a portal, +PORTLET+ a resource that is in relationship with a portlet.

    * An identifier whose meaning depend on the related scope

    ** +SHARED+: possible values should be +common+, +webui+ and +portal+

    ** +PORTAL+: the portal name

    ** +PORTLET+: the portlet name

     

    h4. Graph

     

    The resource graph is a acyclic graph (DAG) that maintains all the identified resources by the portal. The graph maintains the dependency relationship between the various resources. Each resource is able to declare a dependency on another resource.

     

    h4. Relationships

     

    Resources are related now by the concept of **dependency*\* relationship that specifies how scripts are related to each other and how the modular system should be honored.

     

    Later we may add the concept of **include*\* relationship that would describe the fact that a resource provides the same functionality than another resource. A resource could be included exactly once at the moment since multiple inclusion would likely lead to issues (perhaps it would be possible to circumvent that with conditional evaluation somehow).

     

    h4. Loading

     

    At runtime the graph is used to compute the transitive closure of a set of resource in order to determine the minimum set of resource to add to the page. The initial set of resource from which the closure is computed depends on

     

    * The visible portlets: in the page layout / portal layout / shared layout

    * The current portal

    * The resource collected by the JavascriptManager during a request. The JavascriptManager should be used as less as possible as it is really an internal API, applications should really use the declarative manner.

     

    Each entity is associated with a **fetch mode*\* which instructs to the portal how to load the dependency. At the moment we have two fetch modes:

     

    * +IMMEDIATE+ : the script is loaded by the browser specified as a script in the head section

    * +ON_LOAD+ : the script is loaded by the loader that can benefit from parallel loading at the bottom of the page

     

    We may add other fetch modes later that would be:

    * +ON_LOAD_ASYNC+ : it would be like +ON_LOAD+ but not blocking (asynchronous)

    * +ON_CLICK+ : it would load the script when the user interacts with a part of the related entity (like clicking a portlet link). For now we will focus on honouring +ON_LOAD+.

     

    The determined resource set is then loaded by the loader.

     

    h4. Static binding

     

    Portal entities (portlets, portals) can declare:

     

    * Script modules since they care considered as a resource

    * Dependencies on shared resource

     

    h4. Dynamic binding

     

    It is important for a portlet to load scripts in a page. The existing mechanism is to use the JSR 286 header injection however this mechanism has important drawbacks:

     

    * scripts can be loaded multiple times (specially if two portlets in two different war files, load the same scripts, since the only way to identify a script is by its URL)

    * scripts have to be loaded by the head section of the portal

    * scripts cannot take advantage of the loader optimizations (such as parallel loading)

     

    Therefore a portlet to be able to declare the usage of resources during its render phase. We will use the portlet response properties to achieve it with the javaless api (i.e without introducing new Java classes).

     

    {code:language=java}

    public void render(RenderRequest req, RenderResponse resp) throws PortletException, IOException {

       resp.setProperty("org.gatein.javascript.dependecy", "base");

       resp.addProperty("org.gatein.javascript.dependency", "common");

    }

    {code}

     

    This is equivalent to declare:

     

    {code:language=xml}

    <portlet>

        <name>MyPortlet</name>

        <module>

          <depends>

            <module>base</module>

          </depends>

         <depends>

            <module>common</module>

          </depends>

        </module>

      </portlet>

    {code}

     

    h3. Serving

     

    Script serving is served by the navigation controller. An implementation of +WebRequestHandler+ that is invoked by the +WebAppController+ to serve a resource.

     

    h4. Routing

     

    A resource is mapped to a route with the parameters:

     

    * +gtn:scope+: for now the enum value of the scope

    * +gtn:resource+: the resource identifier

    * +gtn:module+: optional value used to distinguish between a merged resource or a particular resource

    * +gtn:compress+: the normal or minified version of the script (which should be able to be empty)

    * +gtn:version+: the global resource version

    * +gtn:lang+: the script locale which can be empty

     

    The current default route used is:

     

    {code}

    <route path="/scripts/{gtn:version}/{gtn:scope}/">

        <route-param qname="gtn:handler">

          <value>script</value>

        </route-param>

        <path-param qname="gtn:version" encoding="preserve-path">

          <pattern>[^/]*</pattern>

        </path-param>

        <route path="/{gtn:resource}{gtn:lang}{gtn:compress}.js">

          <path-param qname="gtn:resource">

            <pattern>[^/]+?</pattern>

          </path-param>

          <path-param qname="gtn:lang" capture-group="true">

            <pattern>-([A-Za-z]{2}(-[A-Za-z]{2})?)|</pattern>

          </path-param>

          <path-param qname="gtn:compress" capture-group="true">

            <pattern>-(min)|</pattern>

          </path-param>

        </route>

        <route path="/{gtn:resource}/{gtn:module}{gtn:lang}{gtn:compress}.js">

          <path-param qname="gtn:module">

            <pattern>[^/]+?</pattern>

          </path-param>

          <path-param qname="gtn:lang" capture-group="true">

            <pattern>-([A-Za-z]{2}(-[A-Za-z]{2})?)|</pattern>

          </path-param>

          <path-param qname="gtn:compress" capture-group="true">

            <pattern>-(min)|</pattern>

          </path-param>

        </route>

      </route>

    {code}

     

    Which gives URL like

     

    * the common shared script merged /portal/scripts/3.3.0/SHARED/common.js

    * the common shared script for the module eXo /portal/scripts/3.3.0/SHARED/common/eXo.JS

     

    h4. Minification

     

    The scripts are minified with the Google Closure Compiler ([http://code.google.com/closure/compiler/]) with the +SIMPLE_OPTIMIZATIONS+ compression level (same used by JQuery). Minified scripts are bound to the \-min.js suffix in order to address both the normal version and the minified version in caches.

     

    h4. Versioning

     

    Scripts are versioned using the GateIn version as defined by the Maven project. It allow to make scripts properly addressable in caches and allow version migration seamlessly. For example, in gatein version: 3.3.0. portal will generate url for SHARED/common like that:

    {code}/portal/scripts/3.3.0/SHARED/common.js{code}

     

    Version can also be configurable. If an user config *tomcat/gatein/conf/configuration.properties* to have this key: *gatein.assets.version=extension-1.0* or add that system property by using *-Dgatein.assets.version*, It will override the default value (GateIn version)

     

    h4. Localization

     

    Resources can be localized by declaring a list of supported locale. When a resource is localized, its URL contains the locale of the script in order to be good with caching. It is important to declare the list of supported locales at the resource level because the portal needs to know which locale are supported by a resource and will use the most appropriate one when creating resource links in the page for the loader.

     

    Each module of a resource is able to declare a resource bundle section, such bundle when present is used to replace properties in the script by values obtained from the resource bundle. The resource bundle declaration happens at the module level, since processing a script with the property resolver could lead to unexpected issues, it is best to keep it scoped at the script granularity.

     

    {code:language=xml}

    <module>

      <name>base</name>

      <supported-locale>en</supported-locale>

      <supported-locale>fr</supported-locale>

      <script>

        <name>eXo.portal.PortalHttpRequest</name>

        <path>/javascript/eXo/portal/PortalHttpRequest.js</path>

        <resource-bundle>eXo.portal</resource-bundle>

      </script>

      ...

    </module>

    {code}

     

    A script like +PortalHttpRequest.js+ can then use the values from the bundle like:

     

    {code:language=javascript}

    if(confirm($\{SessionTimeout})) instance.ajaxTimeout(request) ;

    {code}

     

    The resource bundle should contain:

     

    {code}

    SessionTimeout="Session timeout ! Refresh your browser."

    {code}

     

    The resulting script will

     

    {code:language=javascript}

    if(confirm("Session timeout ! Refresh your browser.")) instance.ajaxTimeout(request) ;

    {code}

     

    {note}The value found in the bundle will be replaced *as is*. In this case we use quotes, but it does not have to. When a property is not resolved for now the $\{} value will be used which will likely cause a JS error (specially when minifying). We will find a more clever way to proceed.{note}

     

    h4. Computation and cache

    *Server cache*

    In order to keep good performance it is important to cache the scripts on the server in memory and avoid multiple computation of the same script by concurrent requests. Indeed the minification of a script is CPU intensive.

     

    We also intent to save scripts in compressed in GZIP format in order to

    * save heap space

    * reduce IO with client

     

    *Client cache*

    Portal also add "Cache-Control" http header in reponsding to resources request (javascript, stylesheet) so resources can be cache effectively on client browser. By default, cache duration will be 86400s (1 day), but it can be config by adding keys to gatein configuration file *tomcat/gatein/conf/configuration.properties* (value is in seconds)

    {code}

    gatein.assets.script.max-age=604800

    gatein.assets.css.max-age=604800

    {code}

    or add system property *-Dgatein.assets.script.max-age=604800*

     

    h3. +Javascript+ object

     

    The +Javascript+ existing object is changed to become a deployment object only. It is not used at runtime anymore since we have introduced a resource graph to replace it. It may be removed in a near future.

     

    h3. XML changes

     

    It is hard to reuse / change the existing XML, so at this point it is wise to introduce new elements in the XSD that are able to express what we need. Such XML will look like

     

    {code:language=xml}

    <!-- A shared script resource -->

      <module>

        <name>common</name>

        <script>

          <name>base</name>

          <path>/base.js</path>

        </script>

      </module>

      <portlet>

        <name>MyPortlet</name>

        <module>

          <mode>on-load</mode>

          <script>

            <name>business</name>

            <path>/business.js</path>

          </script>

          <depends>

            <module>base</module>

          </depends>

        </module>

      </portlet>

    {code}