You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@click.apache.org by sa...@apache.org on 2009/03/21 20:20:00 UTC

svn commit: r756995 - in /incubator/click/trunk/click: extras/src/org/apache/click/extras/filter/ framework/src/org/apache/click/element/ framework/src/org/apache/click/util/

Author: sabob
Date: Sat Mar 21 19:19:59 2009
New Revision: 756995

URL: http://svn.apache.org/viewvc?rev=756995&view=rev
Log:
added application specific resource versioning

Modified:
    incubator/click/trunk/click/extras/src/org/apache/click/extras/filter/PerformanceFilter.java
    incubator/click/trunk/click/framework/src/org/apache/click/element/CssImport.java
    incubator/click/trunk/click/framework/src/org/apache/click/element/JsImport.java
    incubator/click/trunk/click/framework/src/org/apache/click/element/ResourceElement.java
    incubator/click/trunk/click/framework/src/org/apache/click/util/ClickUtils.java

Modified: incubator/click/trunk/click/extras/src/org/apache/click/extras/filter/PerformanceFilter.java
URL: http://svn.apache.org/viewvc/incubator/click/trunk/click/extras/src/org/apache/click/extras/filter/PerformanceFilter.java?rev=756995&r1=756994&r2=756995&view=diff
==============================================================================
--- incubator/click/trunk/click/extras/src/org/apache/click/extras/filter/PerformanceFilter.java (original)
+++ incubator/click/trunk/click/extras/src/org/apache/click/extras/filter/PerformanceFilter.java Sat Mar 21 19:19:59 2009
@@ -48,17 +48,18 @@
  * best practices for speeding up your web site. This filter will enable you to
  * apply the rules:
  * <ul>
- * <li>Rule 3 - Add an Expires Header</li>
- * <li>Rule 4 - Gzip Components</li>
+ * <li><a class="external" target="_blank" href="http://developer.yahoo.com/performance/rules.html#expires">Add an Expires Header</a></li>
+ * <li><a class="external" target="_blank" href="http://developer.yahoo.com/performance/rules.html#gzip">Gzip Components</a></li>
  * </ul>
  *
- * The Click Framework can also help you with the following rules:
+ * Apache Click can also help you with the following rules:
  * <ul>
- * <li>Rule 5 - Put Stylesheets at the Top, by using $cssImports at the
- * top of you page</li>
- * <li>Rule 6 - Put Scripts at the Bottom, by using $jsImports at the bottom
- * of your page</li>
- * <li>Rule 12 - {@link org.apache.click.Control#getHtmlImports()} automatically
+ * <li><a class="external" target="_blank" href="http://developer.yahoo.com/performance/rules.html#css_top">Put Stylesheets at the Top</a>,
+ * by using $headElements at the top of your page</li>
+ * <li><a class="external" target="_blank" href="http://developer.yahoo.com/performance/rules.html#js_bottom">Put Scripts at the Bottom</a>,
+ * by using $jsElements at the bottom of your page</li>
+ * <li><a class="external" target="_blank" href="http://developer.yahoo.com/performance/rules.html#js-dupes">Remove Duplicate Scripts</a>
+ * - {@link org.apache.click.Control#getHeadElements()} automatically
  * removes duplicate scripts.</li>
  * </ul>
  *
@@ -66,54 +67,73 @@
  * This filter will automatically add long expiry headers (5 years) to static Click
  * resources such as CSS style sheets imports, JavaScript imports, and images.
  * This will ensure these resources are cached in the users browser and will not
- * have to be requested again.  With Click static resources are deployed automatically
- * on startup to the web directory <tt style="color:blue;">/click</tt>.
+ * have to be requested again. With Click, static resources are automatically
+ * deployed on startup to the web directory <tt style="color:blue;">/click</tt>.
  * <p/>
- * When the PerformanceFilter is active Click will add a version number to the
- * static resource filenames and apply a long expiry header to these versioned files.
- * When you upgrade the the next version of Click Framework this version number
- * will increment, and the new static resources will be requested and cached by
- * the users browser.
+ * When the PerformanceFilter is active Click will add a <tt>version</tt> number
+ * to the static resource filenames and the long expiry header will be applied to
+ * these versioned files. When you upgrade the the next version of Click, this
+ * version number will increment, and the new static resources will be requested
+ * again and cached by the users browser.
  * <p/>
- * When the PerformanceFilter is not active Click will not include a version number
- * in the static resource filenames and no expiry header will be applied.
+ * When the PerformanceFilter is not active Click will not include a version
+ * number in the static resource filenames and no expiry header will be applied.
  * <p/>
- * The filter will always compress non image static Click resources such as
+ * The filter will always GZIP compress non image, static Click resources, such as
  * style sheets and JavaScript imports.
  *
  * <h3>Configured Static Resources</h3>
- * You can also configure your own applications static resources such as CSS, JS
+ * You can also configure your application's static resources such as CSS, JS
  * files and images to be processed by the filter.
- *
- * Provides a GZIP compression <tt>Filter</tt> to compress HTML ServletResponse
+ * <p/>
+ * This filter will automatically add long expiry headers to configured
+ * resources. The default expiry header is 1 year, but can be changed through
+ * the <tt>init-param</tt> <span class="blue">"cachable-max-age"</span>.
+ * This ensures the resources are cached in the users browser and will not
+ * have to be requested again.
+ * <p/>
+ * The PerformanceFilter provides the ability to add <tt>versioning</tt>
+ * to application specific resources through the
+ * <tt>init-param</tt> <span class="blue">"application-version"</span>.
+ * <p/>
+ * Application versioning is supported by {@link org.apache.click.element.ResourceElement resource elements}
+ * such as {@link org.apache.click.element.JsImport JsImport} and
+ * {@link org.apache.click.element.CssImport CssImport}. When the
+ * <tt>application version</tt> is set, {@link org.apache.click.element.ResourceElement ResourceElements}
+ * will add the <tt>application version</tt> number to their filenames
+ * and PerformanceFilter will apply the long expiry header to these versioned files.
+ * When you increment the <tt>application version</tt>, the resource path will
+ * change and the static resources will be requested again and cached by the
+ * browser.
+ * <p/>
+ * PerformanceFilter provides GZIP compression to compress HTML ServletResponse
  * content. The content will only be compressed if it is bigger than a
- * configurable threshold. The default threshold is 384 bytes.
+ * configurable threshold. The default threshold is 384 bytes but can be changed
+ * through the <tt>init-param</tt> <span class="blue">"compression-threshold"</span>.
  * <p/>
  * Click *.htm pages are automatically compressed by the filter.
  *
  * <h3>Page Template Import References</h3>
  *
  * To import static control references in your page template you simply reference
- * the <tt class="blue">$cssImports</tt> and <tt class="blue">$jsImports</tt>.
+ * the <tt class="blue">$headElements</tt> and <tt class="blue">$jsElements</tt>.
  * For example:
  *
  * <pre class="codeHtml">
  * &lt;html&gt;
  * &lt;head&gt;
- * <span class="blue">$cssImports</span>
+ * <span class="blue">$headElements</span>
  * &lt;/head&gt;
  * &lt;body&gt;
  * <span class="red">$table</span>
  * &lt;/body&gt;
  * &lt;/html&gt;
- * <span class="blue">$jsImports</span></pre>
+ * <span class="blue">$jsElements</span></pre>
  *
- * CSS imports should be included in the head section of your page, and the
- * JavaScript imports should be included at the bottom of your page to support
+ * HEAD elements should be included in the head section of your page, and
+ * JavaScript elements should be included at the bottom of your page to support
  * progressive rendering in the browser.
  *
- * <h3>Click Pages</h3>
- *
  * <h3>Configuration</h3>
  *
  * To configure your application to use the PerformanceFilter include the
@@ -271,6 +291,9 @@
     /** The cachable-path exclude files. */
     protected List excludeFiles = new ArrayList();
 
+    /** The application resource version indicator. */
+    protected String applicationVersionIndicator = "";
+
     // --------------------------------------------------------- Public Methods
 
     /**
@@ -331,7 +354,6 @@
         // Enable resource versioning in Click
         request.setAttribute(ClickUtils.ENABLE_RESOURCE_VERSION, "true");
 
-        // Apply cache expiry Headers
         if (useForeverCacheHeader(path)) {
             setHeaderExpiresCache(response, FOREVER_CACHE_MAX_AGE);
 
@@ -438,6 +460,13 @@
             }
         }
 
+        param = filterConfig.getInitParameter("application-version");
+        if (StringUtils.isNotBlank(param)) {
+            applicationVersionIndicator = ClickUtils.VERSION_INDICATOR_SEP
+                + param;
+            ClickUtils.setApplicationVersion(param);
+        }
+
         param = filterConfig.getInitParameter("cachable-paths");
         if (param != null) {
             String[] paths = StringUtils.split(param, ',');
@@ -535,27 +564,47 @@
     }
 
     /**
+     * Return the application <tt>version indicator</tt> for the specified path.
+     *
+     * @param path the resource path
+     * @return an application version indicator for web resources
+     */
+    protected String getApplicationResourceVersionIndicator(String path) {
+        String indicator = ClickUtils.getApplicationResourceVersionIndicator();
+
+        if (StringUtils.isBlank(indicator)) {
+            // NOTE: getApplicationResourceVersionIndicator will return an empty
+            // string on the first request to this filter because the Context is
+            // not available. Thus we default to the application version
+            // indicator that may have been set on the filter.
+            indicator = applicationVersionIndicator;
+        }
+        return indicator;
+    }
+
+    /**
      * Removes the version indicator from the specified path.
      * <p/>
-     * For example, given the path <tt>'/example/control-1.4.js'</tt>, where
-     * <tt>'-1.4'</tt> is the <tt>version indicator</tt>, this method will
+     * For example, given the path <tt>'/example/control_1.4.js'</tt>, where
+     * <tt>'_1.4'</tt> is the <tt>version indicator</tt>, this method will
      * return <tt>'/example/control.js'</tt>.
      *
      * @see #getResourceVersionIndicator(String)
+     * @see #getApplicationResourceVersionIndicator(java.lang.String)
      *
      * @param path the resource path
      * @return path without the version indicator
      */
     protected String stripResourceVersionIndicator(String path) {
-        int versionIndex =
-            path.lastIndexOf(getResourceVersionIndicator(path));
+        String realPath = path;
 
-        if (versionIndex >= 0) {
-            String extension = path.substring(
-                versionIndex + getResourceVersionIndicator(path).length());
-            return path.substring(0, versionIndex) + extension;
-        }
-        return path;
+        realPath = StringUtils.replace(realPath,
+            getApplicationResourceVersionIndicator(path), "");
+
+        realPath = StringUtils.replace(realPath,
+            getResourceVersionIndicator(path), "");
+
+        return realPath;
     }
 
     /**
@@ -572,17 +621,25 @@
     }
 
     /**
-     * Return true if a path is a static versioned Click resource and should be
+     * Return true if a path is a static versioned resource and should be
      * cached forever.
      *
-     * @see #getResourceVersionIndicator(String)
+     * @see #getResourceVersionIndicator(java.lang.String)
+     * @see #getApplicationResourceVersionIndicator(java.lang.String)
      *
      * @param path the request path to test
      * @return true if the response should be cached forever
      */
     protected boolean useForeverCacheHeader(String path) {
         String versionIndicator = getResourceVersionIndicator(path);
-        return path.startsWith("/click/") && path.indexOf(versionIndicator) != -1;
+        if (path.startsWith("/click/") && path.indexOf(versionIndicator) != -1) {
+            return true;
+        }
+        versionIndicator = getApplicationResourceVersionIndicator(path);
+        if (path.indexOf(versionIndicator) != -1) {
+            return true;
+        }
+        return false;
     }
 
     /**

Modified: incubator/click/trunk/click/framework/src/org/apache/click/element/CssImport.java
URL: http://svn.apache.org/viewvc/incubator/click/trunk/click/framework/src/org/apache/click/element/CssImport.java?rev=756995&r1=756994&r2=756995&view=diff
==============================================================================
--- incubator/click/trunk/click/framework/src/org/apache/click/element/CssImport.java (original)
+++ incubator/click/trunk/click/framework/src/org/apache/click/element/CssImport.java Sat Mar 21 19:19:59 2009
@@ -58,6 +58,10 @@
 
     /**
      * Constructs a new Css import element.
+     * <p/>
+     * The CssImport {@link #setVersionIndicator(java.lang.String) version indicator}
+     * will automatically be set to the
+     * {@link ClickUtils#getApplicationResourceVersionIndicator() application version indicator}.
      */
     public CssImport() {
         this(null);
@@ -67,6 +71,10 @@
      * Construct a new Css import element with the specified <tt>href</tt>
      * attribute.
      * <p/>
+     * The CssImport {@link #setVersionIndicator(java.lang.String) version indicator}
+     * will automatically be set to the
+     * {@link ClickUtils#getApplicationResourceVersionIndicator() application version indicator}.
+     * <p/>
      * <b>Please note</b> if the given <tt>href</tt> begins with a
      * <tt class="wr">"/"</tt> character the href will be prefixed with the web
      * application <tt>context path</tt>.
@@ -74,9 +82,49 @@
      * @param href the Css import href attribute
      */
     public CssImport(String href) {
+        this(href, true);
+    }
+
+    /**
+     * Construct a new Css import element with the specified <tt>href</tt>
+     * attribute.
+     * <p/>
+     * If useApplicationVersionIndicator is true the
+     * CssImport {@link #setVersionIndicator(java.lang.String) version indicator}
+     * will automatically be set to the
+     * {@link ClickUtils#getApplicationResourceVersionIndicator() application version indicator}.
+     * <p/>
+     * <b>Please note</b> if the given <tt>href</tt> begins with a
+     * <tt class="wr">"/"</tt> character the href will be prefixed with the web
+     * application <tt>context path</tt>.
+     *
+     * @param href the Css import href attribute
+     * @param useApplicationVersionIndicator indicates whether the version
+     * indicator will automatically be set to the application version indicator
+     */
+    public CssImport(String href, boolean useApplicationVersionIndicator) {
+        this(href, null);
+        if (useApplicationVersionIndicator) {
+            setVersionIndicator(ClickUtils.getApplicationResourceVersionIndicator());
+        }
+    }
+
+    /**
+     * Construct a new Css import element with the specified <tt>href</tt>
+     * attribute and version indicator.
+     * <p/>
+     * <b>Please note</b> if the given <tt>href</tt> begins with a
+     * <tt class="wr">"/"</tt> character the href will be prefixed with the web
+     * application <tt>context path</tt>.
+     *
+     * @param href the Css import href attribute
+     * @param versionIndicator the version indicator to add to the href path
+     */
+    public CssImport(String href, String versionIndicator) {
         setHref(href);
         setAttribute("type", "text/css");
         setAttribute("rel", "stylesheet");
+        setVersionIndicator(versionIndicator);
     }
 
     // ------------------------------------------------------ Public Properties
@@ -153,6 +201,9 @@
      * @param buffer the buffer to render output to
      */
     public void render(HtmlStringBuffer buffer) {
+        // Add version indicator to the href
+        setHref(addVersionIndicator(getHref()));
+
         renderConditionalCommentPrefix(buffer);
 
         buffer.elementStart(getTag());

Modified: incubator/click/trunk/click/framework/src/org/apache/click/element/JsImport.java
URL: http://svn.apache.org/viewvc/incubator/click/trunk/click/framework/src/org/apache/click/element/JsImport.java?rev=756995&r1=756994&r2=756995&view=diff
==============================================================================
--- incubator/click/trunk/click/framework/src/org/apache/click/element/JsImport.java (original)
+++ incubator/click/trunk/click/framework/src/org/apache/click/element/JsImport.java Sat Mar 21 19:19:59 2009
@@ -58,6 +58,10 @@
 
     /**
      * Constructs a new JavaScript import element.
+     * <p/>
+     * The JsImport {@link #setVersionIndicator(java.lang.String) version indicator}
+     * will automatically be set to the
+     * {@link ClickUtils#getApplicationResourceVersionIndicator() application version indicator}.
      */
     public JsImport() {
         this(null);
@@ -67,6 +71,10 @@
      * Construct a new JavaScript import element with the specified
      * <tt>src</tt> attribute.
      * <p/>
+     * The JsImport {@link #setVersionIndicator(java.lang.String) version indicator}
+     * will automatically be set to the
+     * {@link ClickUtils#getApplicationResourceVersionIndicator() application version indicator}.
+     * <p/>
      * <b>Please note</b> if the given <tt>src</tt> begins with a
      * <tt class="wr">"/"</tt> character the src will be prefixed with the web
      * application <tt>context path</tt>.
@@ -74,8 +82,48 @@
      * @param src the JavaScript import src attribute
      */
     public JsImport(String src) {
+        this(src, true);
+    }
+
+    /**
+     * Construct a new JavaScript import element with the specified <tt>src</tt>
+     * attribute.
+     * <p/>
+     * If useApplicationVersionIndicator is true the
+     * {@link #setVersionIndicator(java.lang.String) version indicator} will
+     * automatically be set to the
+     * {@link ClickUtils#getApplicationResourceVersionIndicator() application version indicator}.
+     * <p/>
+     * <b>Please note</b> if the given <tt>src</tt> begins with a
+     * <tt class="wr">"/"</tt> character the src will be prefixed with the web
+     * application <tt>context path</tt>.
+     *
+     * @param src the JavaScript import src attribute
+     * @param useApplicationVersionIndicator indicates whether the version
+     * indicator will automatically be set to the application version indicator
+     */
+    public JsImport(String src, boolean useApplicationVersionIndicator) {
+        this(src, null);
+        if (useApplicationVersionIndicator) {
+            setVersionIndicator(ClickUtils.getApplicationResourceVersionIndicator());
+        }
+    }
+
+    /**
+     * Construct a new JavaScript import element with the specified <tt>src</tt>
+     * attribute and version indicator.
+     * <p/>
+     * <b>Please note</b> if the given <tt>src</tt> begins with a
+     * <tt class="wr">"/"</tt> character the src will be prefixed with the web
+     * application <tt>context path</tt>.
+     *
+     * @param src the JsImport src attribute
+     * @param versionIndicator the version indicator to add to the src path
+     */
+    public JsImport(String src, String versionIndicator) {
         setSrc(src);
         setAttribute("type", "text/javascript");
+        setVersionIndicator(versionIndicator);
     }
 
     // ------------------------------------------------------ Public properties
@@ -152,6 +200,9 @@
      * @param buffer the buffer to render output to
      */
     public void render(HtmlStringBuffer buffer) {
+        // Add version indicator to the href
+        setSrc(addVersionIndicator(getSrc()));
+
         renderConditionalCommentPrefix(buffer);
 
         buffer.elementStart(getTag());

Modified: incubator/click/trunk/click/framework/src/org/apache/click/element/ResourceElement.java
URL: http://svn.apache.org/viewvc/incubator/click/trunk/click/framework/src/org/apache/click/element/ResourceElement.java?rev=756995&r1=756994&r2=756995&view=diff
==============================================================================
--- incubator/click/trunk/click/framework/src/org/apache/click/element/ResourceElement.java (original)
+++ incubator/click/trunk/click/framework/src/org/apache/click/element/ResourceElement.java Sat Mar 21 19:19:59 2009
@@ -40,11 +40,12 @@
  * using the &lt;style&gt; element.</li>
  * </ul>
  *
+ * <a name="remove-duplicates"></a>
  * <h3>Remove duplicates</h3>
  * Click will ensure that duplicate Resource elements are removed by checking
- * the {@link #isUnique()} property. No matter how many Controls or Pages
- * import the same Resource, only one will be rendered if {@link #isUnique()}
- * returns <tt>true</tt>.
+ * the {@link #isUnique()} property. Thus if the same Resource is imported
+ * mutliple times by the Page or different Controls, only one Resource will be
+ * rendered, if {@link #isUnique()} returns <tt>true</tt>.
  * <p/>
  * The rules for defining a unique Resource is as follows:
  * <ul>
@@ -97,6 +98,33 @@
  *     }
  * } </pre>
  *
+ * <a name="versioning"></a>
+ * <h3>Automatic Resource versioning</h3>
+ *
+ * ResourceElement provides the ability to automatically version elements
+ * according to Yahoo Performance Rule: <a target="_blank" href="http://developer.yahoo.com/performance/rules.html#expires">Add an Expires or a Cache-Control Header</a>.
+ * This rule recommends adding an expiry header to JavaScript, Css
+ * and image resources, which forces the browser to cache the resources. It also
+ * suggests <em>versioning</em> the resources so that each new release of the
+ * web application renders resources with different paths, forcing the browser
+ * to download the new resources.
+ * <p/>
+ * For detailed information on versioning JavaScript, Css and image resources
+ * see the <a href="../../../../extras-api/org/apache/click/extras/filter/PerformanceFilter.html">PerformanceFilter</a>.
+ * <p/>
+ * To enable versioning of JavaScript, Css and image resources the following
+ * conditions must be met:
+ * <ul>
+ * <li>the {@link org.apache.click.util.ClickUtils#ENABLE_RESOURCE_VERSION}
+ * request attribute must be set to <tt>true</tt></li>
+ * <li>the application mode must be either "production" or "profile"</li>
+ * <li>the {@link org.apache.click.util.ClickUtils#setApplicationVersion(java.lang.String)
+ * application version} must be set</li>
+ * </ul>
+ * <b>Please note:</b> <a href="../../../../extras-api/org/apache/click/extras/filter/PerformanceFilter.html">PerformanceFilter</a>
+ * handles the above steps for you.
+ *
+ * <a name="conditional-comment"></a>
  * <h3>Conditional comment support for Internet Explorer</h3>
  *
  * Sometimes it is necessary to provide additional JavaScript and Css for
@@ -176,9 +204,35 @@
      */
     private boolean unique = false;
 
+    /**
+     * The <tt>version indicator</tt> to append to the Resource element.
+     */
+    private String versionIndicator;
+
     // ------------------------------------------------------ Public properties
 
     /**
+     * Return the <tt>version indicator</tt> to be appended to the resource
+     * path.
+     *
+     * @return the <tt>version indicator</tt> to be appended to the resource
+     * path.
+     */
+    public String getVersionIndicator() {
+        return versionIndicator;
+    }
+
+    /**
+     * Set the <tt>version indicator</tt> to be appended to the resource path.
+     *
+     * @param versionIndicator the version indicator to be appended to the
+     * resource path
+     */
+    public void setVersionIndicator(String versionIndicator) {
+        this.versionIndicator = versionIndicator;
+    }
+
+    /**
      * Return true if the Resource should be unique, false otherwise. The default
      * value is <tt>true</tt> if the {@link #getId() ID} attribute is defined,
      * false otherwise.
@@ -243,6 +297,34 @@
     // ------------------------------------------------ Package Private Methods
 
     /**
+     * Add the {@link #getApplicationResourceVersionIndicator(org.apache.click.Context)}
+     * to the specified resourcePath. If the version indicator is not defined
+     * this method will return the resourcePath unchanged.
+     *
+     * @param resourcePath the resource path to add the version indicator to
+     */
+    String addVersionIndicator(String resourcePath) {
+        String versionIndicator = getVersionIndicator();
+
+        // If no resourcePath or version indicator is defined, exit early
+        if (resourcePath == null || StringUtils.isBlank(versionIndicator)) {
+            return resourcePath;
+        }
+
+        int start = resourcePath.lastIndexOf(".");
+        // Exit early if extension is not found
+        if (start < 0) {
+            return resourcePath;
+        }
+
+        HtmlStringBuffer buffer = new HtmlStringBuffer();
+        buffer.append(resourcePath.substring(0, start));
+        buffer.append(versionIndicator);
+        buffer.append(resourcePath.substring(start));
+        return buffer.toString();
+    }
+
+    /**
      * Render the {@link #getConditionalComment() conditional comment} prefix
      * to the specified buffer. If the conditional comment is not defined this
      * method won't append to the buffer.

Modified: incubator/click/trunk/click/framework/src/org/apache/click/util/ClickUtils.java
URL: http://svn.apache.org/viewvc/incubator/click/trunk/click/framework/src/org/apache/click/util/ClickUtils.java?rev=756995&r1=756994&r2=756995&view=diff
==============================================================================
--- incubator/click/trunk/click/framework/src/org/apache/click/util/ClickUtils.java (original)
+++ incubator/click/trunk/click/framework/src/org/apache/click/util/ClickUtils.java Sat Mar 21 19:19:59 2009
@@ -107,15 +107,24 @@
      */
     public static final String DEFAULT_APP_CONFIG = "/WEB-INF/click.xml";
 
+    /** The version indicator separator string. */
+    public static final String VERSION_INDICATOR_SEP = "_";
+
     /** The static web resource version number indicator string. */
     public static final String RESOURCE_VERSION_INDICATOR =
-        "_" + getClickVersion();
+        VERSION_INDICATOR_SEP + getClickVersion();
 
     // ------------------------------------------------------ Private Constants
 
     /** The cached resource version indicator. */
     private static String cachedResourceVersionIndicator;
 
+    /** The static application-wide resource version indicator. */
+    private static String applicationVersion;
+
+    /** The cached application version indicator string. */
+    private static String cachedApplicationVersionIndicator;
+
     /**
      * Character used to separate username and password in persistent cookies.
      * 0x13 == "Device Control 3" non-printing ASCII char. Unlikely to appear
@@ -891,10 +900,37 @@
     }
 
     /**
-     * Return a version indicator for static web resources
-     * (eg css, js and image files), if resource versioning is active,
+     * Return the web application version string.
+     *
+     * @return the web application version string
+     */
+    public static String getApplicationVersion() {
+        return applicationVersion;
+    }
+
+    /**
+     * Set the web application version string.
+     *
+     * @param applicationVersion the web application version string
+     */
+    public static void setApplicationVersion(String applicationVersion) {
+        ClickUtils.applicationVersion = applicationVersion;
+        cachedApplicationVersionIndicator = null;
+    }
+
+    /**
+     * Return Click's version indicator for static web resources
+     * (eg css, js and image files) if resource versioning is active,
      * otherwise this method will return an empty string.
      * <p/>
+     * Click's resource versioning becomes active under the following
+     * conditions:
+     * <ul>
+     * <li>the {@link #ENABLE_RESOURCE_VERSION} request attribute must be set
+     * to <tt>true</tt></li>
+     * <li>the application mode must be either "production" or "profile"</li>
+     * </ul>
+     *
      * The version indicator is based on the current Click release version.
      * For example when using Click 1.4 this method will return the string
      * <tt>"_1.4"</tt>.
@@ -924,6 +960,55 @@
     }
 
     /**
+     * If resource versioning is active this method will return the
+     * application version indicator for static web resources
+     * (eg JavaScript and Css) otherwise this method will return an empty string.
+     * <p/>
+     * Application resource versioning becomes active under the following
+     * conditions:
+     * <ul>
+     * <li>the {@link #ENABLE_RESOURCE_VERSION} request attribute must be set
+     * to <tt>true</tt></li>
+     * <li>the application mode must be either "production" or "profile"</li>
+     * </ul>
+     *
+     * The version indicator is based on the application version.
+     * For example if the application version is 1.2 this method will
+     * return the string <tt>"_1.2"</tt>.
+     * <p/>
+     * The application version can be set through the static method
+     * {@link #setApplicationVersion(java.lang.String)}.
+     *
+     * @return an application version indicator for web resources
+     */
+    public static String getApplicationResourceVersionIndicator() {
+        // Return the cached version first
+        if (cachedApplicationVersionIndicator != null) {
+            return cachedApplicationVersionIndicator;
+        }
+
+        // Check if the Context has been set
+        if (Context.hasThreadLocalContext()) {
+
+            Context context = Context.getThreadLocalContext();
+            ConfigService configService = ClickUtils.getConfigService(context.getServletContext());
+
+            boolean isProductionModes = configService.isProductionMode()
+                || configService.isProfileMode();
+
+            if (isProductionModes && ClickUtils.isEnableResourceVersion(context)) {
+                String version = getApplicationVersion();
+                if (StringUtils.isNotBlank(version)) {
+                    cachedApplicationVersionIndicator = VERSION_INDICATOR_SEP
+                        + version;
+                    return cachedApplicationVersionIndicator;
+                }
+            }
+        }
+        return "";
+    }
+
+    /**
      * Populate the given object's attributes with the Form's field values.
      * <p/>
      * The specified Object can either be a POJO (plain old java object) or