You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by dd...@apache.org on 2012/01/13 18:59:32 UTC

svn commit: r1231211 - in /shindig/trunk: content/samplecontainer/examples/commoncontainer/ features/src/main/javascript/features/container.gadget/ features/src/main/javascript/features/container.util/ features/src/main/javascript/features/container/ f...

Author: ddumont
Date: Fri Jan 13 17:59:32 2012
New Revision: 1231211

URL: http://svn.apache.org/viewvc?rev=1231211&view=rev
Log:
SHINDIG-1681 Implement Container token Refresh and moduleId capability for gadget tokens.

Added:
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleIdManager.java   (with props)
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleIdManagerImpl.java   (with props)
Modified:
    shindig/trunk/content/samplecontainer/examples/commoncontainer/assembler.js
    shindig/trunk/features/src/main/javascript/features/container.gadget/gadget_holder.js
    shindig/trunk/features/src/main/javascript/features/container.gadget/gadget_site.js
    shindig/trunk/features/src/main/javascript/features/container.util/constant.js
    shindig/trunk/features/src/main/javascript/features/container.util/feature.xml
    shindig/trunk/features/src/main/javascript/features/container.util/util.js
    shindig/trunk/features/src/main/javascript/features/container/container.js
    shindig/trunk/features/src/main/javascript/features/container/feature.xml
    shindig/trunk/features/src/main/javascript/features/container/service.js
    shindig/trunk/features/src/test/javascript/features/container/gadget_holder_test.js
    shindig/trunk/features/src/test/javascript/features/container/service_test.js
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandler.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerService.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerServiceTest.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java

Modified: shindig/trunk/content/samplecontainer/examples/commoncontainer/assembler.js
URL: http://svn.apache.org/viewvc/shindig/trunk/content/samplecontainer/examples/commoncontainer/assembler.js?rev=1231211&r1=1231210&r2=1231211&view=diff
==============================================================================
--- shindig/trunk/content/samplecontainer/examples/commoncontainer/assembler.js (original)
+++ shindig/trunk/content/samplecontainer/examples/commoncontainer/assembler.js Fri Jan 13 17:59:32 2012
@@ -25,16 +25,20 @@ var testConfig = testConfig || {};
 testConfig[osapi.container.ServiceConfig.API_PATH] = contextRoot + '/rpc';
 testConfig[osapi.container.ContainerConfig.RENDER_DEBUG] = '1';
 
+// Default the security token for the container. Using this example security token requires enabling
+// the DefaultSecurityTokenCodec to let UrlParameterAuthenticationHandler create valid security token.
+// 10 seconds is fast, but this is mostly for demonstration purposes.
+testConfig[osapi.container.ContainerConfig.GET_CONTAINER_TOKEN] = function(callback) {
+  console.log('Updating container security token.');
+  callback('john.doe:john.doe:appid:cont:url:0:default', 10);
+};
+
 //  Create the new CommonContainer
 var CommonContainer = new osapi.container.Container(testConfig);
 
 //Gadget site to title id map
 var siteToTitleMap = {};
 
-// Default the security token for the container. Using this example security token requires enabling
-// the DefaultSecurityTokenCodec to let UrlParameterAuthenticationHandler create valid security token.
-shindig.auth.updateSecurityToken('john.doe:john.doe:appid:cont:url:0:default');
-
 // Need to pull these from values supplied in the dialog
 CommonContainer.init = function() {
 

Modified: shindig/trunk/features/src/main/javascript/features/container.gadget/gadget_holder.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container.gadget/gadget_holder.js?rev=1231211&r1=1231210&r2=1231211&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container.gadget/gadget_holder.js (original)
+++ shindig/trunk/features/src/main/javascript/features/container.gadget/gadget_holder.js Fri Jan 13 17:59:32 2012
@@ -23,18 +23,19 @@
 
 
 /**
- * @param {number} siteId The id of site containing this holder.
+ * @param {osapi.container.GadgetSite} site The site containing this holder.
  * @param {Element} el The element to render gadgets in.
  * @param {string} onGadgetLoad The name of the on load function
  * @constructor
  */
-osapi.container.GadgetHolder = function(siteId, el, onGadgetLoad) {
+osapi.container.GadgetHolder = function(site, el, onGadgetLoad) {
+
   /**
-   * Unique numeric gadget ID.
-   * @type {number}
+   * The gadget site that holds this holder
+   * @type {osapi.container.GadgetSite}
    * @private
    */
-  this.siteId_ = siteId;
+  this.site_ = site;
 
   /**
    * The element into which the gadget is rendered.
@@ -183,7 +184,7 @@ osapi.container.GadgetHolder.prototype.s
 osapi.container.GadgetHolder.prototype.render = function(
     gadgetInfo, viewParams, renderParams) {
   this.iframeId_ = osapi.container.GadgetHolder.IFRAME_ID_PREFIX_ +
-      this.siteId_;
+      this.site_.getId();
   this.gadgetInfo_ = gadgetInfo;
   this.viewParams_ = viewParams;
   this.renderParams_ = renderParams;
@@ -348,7 +349,7 @@ osapi.container.GadgetHolder.prototype.g
   }
 
   // Uniquely identify possibly-same gadgets on a page.
-  uri.setQP('mid', String(this.siteId_));
+  uri.setQP('mid', String(this.site_.getModuleId()));
 
   if (!osapi.container.util.isEmptyJson(this.viewParams_)) {
     var gadgetParamText = gadgets.json.stringify(this.viewParams_);

Modified: shindig/trunk/features/src/main/javascript/features/container.gadget/gadget_site.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container.gadget/gadget_site.js?rev=1231211&r1=1231210&r2=1231211&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container.gadget/gadget_site.js (original)
+++ shindig/trunk/features/src/main/javascript/features/container.gadget/gadget_site.js Fri Jan 13 17:59:32 2012
@@ -78,6 +78,15 @@ osapi.container.GadgetSite = function(ar
     osapi.container.Container.prototype.nextUniqueSiteId_++;
 
   /**
+   * Unique numeric module ID for this gadget instance.  A module id is used to
+   * identify persisted instances of gadgets.
+   *
+   * @type {number}
+   * @private
+   */
+  this.moduleId_ = 0;
+
+  /**
    * ID of parent gadget.
    * @type {string?}
    * @private
@@ -168,6 +177,52 @@ osapi.container.GadgetSite.prototype.get
   return this.id_;
 };
 
+/**
+ * @return {undefined|null|number} The numerical moduleId of this gadget, if
+ *   set.  May return null or undefined if not set.
+ */
+osapi.container.GadgetSite.prototype.getModuleId = function() {
+  return this.moduleId_;
+};
+
+/**
+ * If you want to change the moduleId after a gadget has rendered, re-navigate the site.
+ *
+ * @param {string} url This gadget's url (may not yet be accessible in all cases from the holder).
+ * @param {number} mid The numerical moduleId for this gadget to use.
+ * @param {function} opt_callback Optional callback to run when the moduleId is set.
+ * @private
+ */
+osapi.container.GadgetSite.prototype.setModuleId_ = function(url, mid, opt_callback) {
+  if (mid && this.moduleId_ != mid) {
+    var self = this,
+        url = osapi.container.util.buildTokenRequestUrl(url, mid);
+
+    if (!self.service_.getCachedGadgetToken(url)) {
+      // We need to request a security token for this gadget instance.
+      var request = osapi.container.util.newTokenRequest([url]);
+      self.service_.getGadgetToken(request, function(response) {
+        var ttl, mid;
+        if (response && response[url]) {
+          if (ttl = response[url][osapi.container.TokenResponse.TOKEN_TTL]) {
+            self.container_.scheduleRefreshTokens_(ttl);
+          }
+          var mid = response[url][osapi.container.TokenResponse.MODULE_ID];
+          if (mid || mid == 0) {
+            self.moduleId_ = mid;
+          }
+        }
+        if (opt_callback) {
+          opt_callback();
+        }
+      });
+      return;
+    }
+  }
+  if (opt_callback) {
+    opt_callback();
+  }
+};
 
 /**
  * Returns the currently-active gadget, the loading gadget if a gadget is
@@ -224,9 +279,12 @@ osapi.container.GadgetSite.prototype.nav
       var message = ['Failed to navigate for gadget ', gadgetUrl, '.'].join('');
       osapi.container.util.warn(message);
     } else {
-      self.container_.applyLifecycleCallbacks_(osapi.container.CallbackType.ON_BEFORE_RENDER,
-              gadgetInfo);
-      self.render(gadgetInfo, viewParams, renderParams);
+      var moduleId = renderParams[osapi.container.RenderParam.MODULE_ID] || 0;
+      self.setModuleId_(gadgetUrl, moduleId, function() {
+        self.container_.applyLifecycleCallbacks_(osapi.container.CallbackType.ON_BEFORE_RENDER,
+                gadgetInfo);
+        self.render(gadgetInfo, viewParams, renderParams);
+      });
     }
 
     // Return metadata server response time.
@@ -326,7 +384,7 @@ osapi.container.GadgetSite.prototype.ren
 
   // Load into the double-buffer if there is one.
   var el = this.loadingGadgetEl_ || this.currentGadgetEl_;
-  this.loadingGadgetHolder_ = new osapi.container.GadgetHolder(this.id_, el,
+  this.loadingGadgetHolder_ = new osapi.container.GadgetHolder(this, el,
           this.gadgetOnLoad_);
 
   var localRenderParams = {};
@@ -396,8 +454,10 @@ osapi.container.GadgetSite.prototype.rpc
  * @private
  */
 osapi.container.GadgetSite.prototype.updateSecurityToken_ =
-      function(gadgetInfo, renderParams) {
-  var tokenInfo = this.service_.getCachedGadgetToken(gadgetInfo['url']);
+    function(gadgetInfo, renderParams) {
+  var url = osapi.container.util.buildTokenRequestUrl(gadgetInfo['url'], this.moduleId_),
+      tokenInfo = this.service_.getCachedGadgetToken(url);
+
   if (tokenInfo) {
     var token = tokenInfo[osapi.container.TokenResponse.TOKEN];
     this.loadingGadgetHolder_.setSecurityToken(token);

Modified: shindig/trunk/features/src/main/javascript/features/container.util/constant.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container.util/constant.js?rev=1231211&r1=1231210&r2=1231211&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container.util/constant.js (original)
+++ shindig/trunk/features/src/main/javascript/features/container.util/constant.js Fri Jan 13 17:59:32 2012
@@ -66,7 +66,9 @@ osapi.container.MetadataResponse = {
  * @enum {string}
  */
 osapi.container.TokenResponse = {
-  TOKEN: 'token'
+  TOKEN: 'token',
+  TOKEN_TTL: 'tokenTTL',
+  MODULE_ID: 'moduleId'
 };
 
 
@@ -130,7 +132,13 @@ osapi.container.RenderParam = {
     VIEW: 'view',
 
     /** The starting gadget iframe width (in pixels). */
-    WIDTH: 'width'
+    WIDTH: 'width',
+
+    /**
+     * The modduleId of this gadget.  Used to identify saved instances of gadgets.
+     * Defaults to 0, which means the instance of the gadget is not saved.
+     */
+    MODULE_ID: 'moduleid'
 };
 
 /**
@@ -179,3 +187,179 @@ osapi.container.CallbackType = {
     /** Name of the global function all gadgets will call when they are loaded. */
     GADGET_ON_LOAD: '__gadgetOnLoad'
 };
+
+/**
+ * Enumeration of configuration keys for a osapi.container.Container. This is specified in
+ * JSON to provide extensible configuration. These enum values are for
+ * documentation purposes only, it is expected that clients use the string
+ * values.
+ * @enum {string}
+ */
+osapi.container.ContainerConfig = {
+  /**
+   * Allow gadgets to render in unspecified view.
+   * @type {string}
+   * @const
+   */
+  ALLOW_DEFAULT_VIEW: 'allowDefaultView',
+
+  /**
+   * Whether cajole mode is turned on.
+   * @type {string}
+   * @const
+   */
+  RENDER_CAJOLE: 'renderCajole',
+
+  /**
+   * Whether debug mode is turned on.
+   * @type {string}
+   * @const
+   */
+  RENDER_DEBUG: 'renderDebug',
+
+  /**
+   * The debug param name to look for in container URL for per-request debugging.
+   * @type {string}
+   * @const
+   */
+  RENDER_DEBUG_PARAM: 'renderDebugParam',
+
+  /**
+   * Whether test mode is turned on.
+   * @type {string}
+   * @const
+   */
+  RENDER_TEST: 'renderTest',
+
+  /**
+   * Security token refresh interval (in ms). Set to 0 in config to disable
+   * token refresh.
+   *
+   * This number should always be >= 0. The smallest encountered token ttl or this
+   * number will be used as the refresh interval, whichever is smaller.
+   *
+   * @type {string}
+   * @const
+   */
+  TOKEN_REFRESH_INTERVAL: 'tokenRefreshInterval',
+
+  /**
+   * Globally-defined callback function upon gadget navigation. Useful to
+   * broadcast timing and stat information back to container.
+   * @type {string}
+   * @const
+   */
+  NAVIGATE_CALLBACK: 'navigateCallback',
+
+  /**
+   * Provide server reference time for preloaded data.
+   * This time is used instead of each response time in order to support server
+   * caching of results.
+   * @type {number}
+   * @const
+   */
+  PRELOAD_REF_TIME: 'preloadRefTime',
+
+  /**
+   * Preloaded hash of gadgets metadata
+   * @type {Object}
+   * @const
+   */
+  PRELOAD_METADATAS: 'preloadMetadatas',
+
+  /**
+   * Preloaded hash of gadgets tokens
+   * @type {Object}
+   * @const
+   */
+  PRELOAD_TOKENS: 'preloadTokens',
+
+  /**
+   * Used to query the language locale part of the container page.
+   * @type {function}
+   */
+  GET_LANGUAGE: 'GET_LANGUAGE',
+
+  /**
+   * Used to query the country locale part of the container page.
+   * @type {function}
+   */
+  GET_COUNTRY: 'GET_COUNTRY',
+
+  /**
+   * Used to retrieve the persisted preferences for a gadget.
+   * @type {function}
+   */
+  GET_PREFERENCES: 'GET_PREFERENCES',
+
+  /**
+   * Used to persist preferences for a gadget.
+   * @type {function}
+   */
+  SET_PREFERENCES: 'SET_PREFERENCES',
+
+  /**
+   * Used to arbitrate RPC calls.
+   * @type {function}
+   */
+  RPC_ARBITRATOR: 'rpcArbitrator',
+
+  /**
+   * Used to retrieve security tokens for gadgets.
+   * @type {function}
+   */
+  GET_GADGET_TOKEN: 'GET_GADGET_TOKEN',
+
+  /**
+   * Used to retrieve a security token for the container.
+   * Containers who specify this config value can call
+   * CommonContainer.updateContainerSecurityToken after the creation of the
+   * common container to start the scheduling of container token refreshes.
+   *
+   * @type {function(function)=}
+   * @param {function(String, number)} callback The function to call to report
+   *   the updated token and the token's new time-to-live in seconds. This
+   *   callback function must be called with unspecified values in the event of
+   *   an error.
+   *
+   *   The first and second arguments to this callback function are the same as
+   *   the second and third arguments to:
+   *     osapi.container.Container.prototype.updateContainerSecurityToken
+   *   Example:
+   *   <code>
+   *     var config = {};
+   *     config[osapi.container.ContainerConfig.GET_CONTAINER_TOKEN] = function(result) {
+   *       var token, ttl, error = false;
+   *       // Do work to set token and ttl values
+   *       if (error) {
+   *         result();
+   *       } else {
+   *         result(token, ttl);
+   *       }
+   *     };
+   *   </code>
+   * @see osapi.container.Container.prototype.updateContainerSecurityToken
+   */
+  GET_CONTAINER_TOKEN: 'GET_CONTAINER_TOKEN'
+};
+
+/**
+ * Enumeration of configuration keys for a osapi.container.Service. This is specified in
+ * JSON to provide extensible configuration.
+ * @enum {string}
+ */
+osapi.container.ServiceConfig = {
+  /**
+   * Host to fetch gadget information, via XHR.
+   * @type {string}
+   * @const
+   */
+  API_HOST: 'apiHost',
+
+  /**
+   * Path to fetch gadget information, via XHR.
+   * @type {string}
+   * @const
+   */
+  API_PATH: 'apiPath'
+}
\ No newline at end of file

Modified: shindig/trunk/features/src/main/javascript/features/container.util/feature.xml
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container.util/feature.xml?rev=1231211&r1=1231210&r2=1231211&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container.util/feature.xml (original)
+++ shindig/trunk/features/src/main/javascript/features/container.util/feature.xml Fri Jan 13 17:59:32 2012
@@ -39,6 +39,30 @@ specific language governing permissions 
       <exports type="js">osapi.container.RenderParam.USER_PREFS</exports>
       <exports type="js">osapi.container.RenderParam.VIEW</exports>
       <exports type="js">osapi.container.RenderParam.WIDTH</exports>
+      <exports type="js">osapi.container.RenderParam.MODULE_ID</exports>
+
+      <exports type="js">osapi.container.ViewParam.VIEW</exports>
+
+      <exports type="js">osapi.container.ContainerConfig.ALLOW_DEFAULT_VIEW</exports>
+      <exports type="js">osapi.container.ContainerConfig.RENDER_CAJOLE</exports>
+      <exports type="js">osapi.container.ContainerConfig.RENDER_DEBUG</exports>
+      <exports type="js">osapi.container.ContainerConfig.RENDER_DEBUG_PARAM</exports>
+      <exports type="js">osapi.container.ContainerConfig.RENDER_TEST</exports>
+      <exports type="js">osapi.container.ContainerConfig.TOKEN_REFRESH_INTERVAL</exports>
+      <exports type="js">osapi.container.ContainerConfig.NAVIGATE_CALLBACK</exports>
+      <exports type="js">osapi.container.ContainerConfig.PRELOAD_REF_TIME</exports>
+      <exports type="js">osapi.container.ContainerConfig.PRELOAD_METADATAS</exports>
+      <exports type="js">osapi.container.ContainerConfig.PRELOAD_TOKENS</exports>
+      <exports type="js">osapi.container.ContainerConfig.GET_LANGUAGE</exports>
+      <exports type="js">osapi.container.ContainerConfig.GET_COUNTRY</exports>
+      <exports type="js">osapi.container.ContainerConfig.GET_PREFERENCES</exports>
+      <exports type="js">osapi.container.ContainerConfig.SET_PREFERENCES</exports>
+      <exports type="js">osapi.container.ContainerConfig.RPC_ARBITRATOR</exports>
+      <exports type="js">osapi.container.ContainerConfig.GET_GADGET_TOKEN</exports>
+      <exports type="js">osapi.container.ContainerConfig.GET_CONTAINER_TOKEN</exports>
+
+      <exports type="js">osapi.container.ServiceConfig.API_HOST</exports>
+      <exports type="js">osapi.container.ServiceConfig.API_PATH</exports>
     </api>
   </container>
 </feature>
\ No newline at end of file

Modified: shindig/trunk/features/src/main/javascript/features/container.util/util.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container.util/util.js?rev=1231211&r1=1231210&r2=1231211&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container.util/util.js (original)
+++ shindig/trunk/features/src/main/javascript/features/container.util/util.js Fri Jan 13 17:59:32 2012
@@ -101,7 +101,9 @@ osapi.container.util.newTokenRequest = f
     'container': window.__CONTAINER,
     'ids': gadgetUrls,
     'fields': [
-      'token'
+      'token',
+      'tokenTTL',
+      'moduleId'
     ]
   };
 };
@@ -162,7 +164,7 @@ osapi.container.util.getCurrentTimeMs = 
 };
 
 /**
- * Crates the HTML for the iFrame
+ * Creates the HTML for the iFrame
  * @param {Object.<string,string>} iframeParams iframe Params.
  * @return {string} the HTML for the iFrame.
  */
@@ -187,3 +189,18 @@ osapi.container.util.createIframeHtml = 
 
   return out.join('');
 };
+
+/**
+ * Constructs a url for token refresh given a gadgetUrl and moduleId.
+ *
+ * @param {string} url The gadget's url
+ * @param {number} moduleId A moduleId.
+ * @return {string} A url to use in a TokenRequest for a security token.
+ */
+osapi.container.util.buildTokenRequestUrl = function(url, moduleId) {
+  url = shindig.uri(url);
+  if (moduleId) {
+    url.setFragment(moduleId);
+  }
+  return url.toString();
+};

Modified: shindig/trunk/features/src/main/javascript/features/container/container.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container/container.js?rev=1231211&r1=1231210&r2=1231211&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container/container.js (original)
+++ shindig/trunk/features/src/main/javascript/features/container/container.js Fri Jan 13 17:59:32 2012
@@ -103,12 +103,7 @@ osapi.container.Container = function(opt
       osapi.container.ContainerConfig.RENDER_TEST, false));
 
   /**
-   * Security token refresh interval (in ms). Set to < 0 in config to disable
-   * token refresh.
-   *
-   * Provided this number is >= 0, the smallest encountered token ttl or this
-   * number will be used as the refresh interval, whichever is smaller.
-   *
+   * @see osapi.container.ContainerConfig.TOKEN_REFRESH_INTERVAL
    * @type {number}
    * @private
    */
@@ -134,10 +129,10 @@ osapi.container.Container = function(opt
    * @type {osapi.container.Service}
    * @private
    */
-  this.service_ = new osapi.container.Service(config);
+  this.service_ = new osapi.container.Service(this);
 
   /**
-   * result from calling window.setInterval()
+   * result from calling window.setTimeout()
    * @type {?number}
    * @private
    */
@@ -222,7 +217,6 @@ osapi.container.Container.prototype.navi
 
   var
     self = this,
-    selfSite = site,
     finishNavigate = function(preferences) {
       renderParams[RenderParam.USER_PREFS] = preferences;
       self.applyLifecycleCallbacks_(osapi.container.CallbackType.ON_BEFORE_NAVIGATE,
@@ -239,8 +233,7 @@ osapi.container.Container.prototype.navi
           self.scheduleRefreshTokens_(gadgetInfo[osapi.container.MetadataResponse.TOKEN_TTL]);
         }
 
-        self.applyLifecycleCallbacks_(osapi.container.CallbackType.ON_NAVIGATED,
-            selfSite);
+        self.applyLifecycleCallbacks_(osapi.container.CallbackType.ON_NAVIGATED, site);
         callback(gadgetInfo);
       });
     };
@@ -413,114 +406,6 @@ osapi.container.Container.addMixin = fun
 
 
 // -----------------------------------------------------------------------------
-// Valid JSON keys.
-// -----------------------------------------------------------------------------
-
-/**
- * Enumeration of configuration keys for this container. This is specified in
- * JSON to provide extensible configuration. These enum values are for
- * documentation purposes only, it is expected that clients use the string
- * values.
- * @enum {string}
- */
-osapi.container.ContainerConfig = {};
-/**
- * Allow gadgets to render in unspecified view.
- * @type {string}
- * @const
- */
-osapi.container.ContainerConfig.ALLOW_DEFAULT_VIEW = 'allowDefaultView';
-/**
- * Whether cajole mode is turned on.
- * @type {string}
- * @const
- */
-osapi.container.ContainerConfig.RENDER_CAJOLE = 'renderCajole';
-/**
- * Whether debug mode is turned on.
- * @type {string}
- * @const
- */
-osapi.container.ContainerConfig.RENDER_DEBUG = 'renderDebug';
-/**
- * The debug param name to look for in container URL for per-request debugging.
- * @type {string}
- * @const
- */
-osapi.container.ContainerConfig.RENDER_DEBUG_PARAM = 'renderDebugParam';
-/**
- * Whether test mode is turned on.
- * @type {string}
- * @const
- */
-osapi.container.ContainerConfig.RENDER_TEST = 'renderTest';
-/**
- * Security token refresh interval (in ms) for debugging.
- * @type {string}
- * @const
- */
-osapi.container.ContainerConfig.TOKEN_REFRESH_INTERVAL = 'tokenRefreshInterval';
-/**
- * Globally-defined callback function upon gadget navigation. Useful to
- * broadcast timing and stat information back to container.
- * @type {string}
- * @const
- */
-osapi.container.ContainerConfig.NAVIGATE_CALLBACK = 'navigateCallback';
-
-/**
- * Provide server reference time for preloaded data.
- * This time is used instead of each response time in order to support server
- * caching of results.
- * @type {number}
- * @const
- */
-osapi.container.ContainerConfig.PRELOAD_REF_TIME = 'preloadRefTime';
-/**
- * Preloaded hash of gadgets metadata
- * @type {Object}
- * @const
- */
-osapi.container.ContainerConfig.PRELOAD_METADATAS = 'preloadMetadatas';
-/**
- * Preloaded hash of gadgets tokens
- * @type {Object}
- * @const
- */
-osapi.container.ContainerConfig.PRELOAD_TOKENS = 'preloadTokens';
-/**
- * Used to query the language locale part of the container page.
- * @type {function}
- */
-osapi.container.ContainerConfig.GET_LANGUAGE = 'GET_LANGUAGE';
-/**
- * Used to query the country locale part of the container page.
- * @type {function}
- */
-osapi.container.ContainerConfig.GET_COUNTRY = 'GET_COUNTRY';
-/**
- * Used to retrieve the persisted preferences for a gadget.
- * @type {function}
- */
-osapi.container.ContainerConfig.GET_PREFERENCES = 'GET_PREFERENCES';
-/**
- * Used to persist preferences for a gadget.
- * @type {function}
- */
-osapi.container.ContainerConfig.SET_PREFERENCES = 'SET_PREFERENCES';
-/**
- * Used to arbitrate RPC calls.
- * @type {function}
- */
-osapi.container.ContainerConfig.RPC_ARBITRATOR = 'rpcArbitrator';
-/**
- * Used to retrieve security tokens for gadgets.
- * @type {function}
- */
-osapi.container.ContainerConfig.GET_GADGET_TOKEN = 'GET_GADGET_TOKEN';
-
-
-// -----------------------------------------------------------------------------
 // Private variables and methods.
 // -----------------------------------------------------------------------------
 
@@ -624,6 +509,22 @@ osapi.container.Container.prototype.getS
 };
 
 /**
+ * Update and schedule refreshing of container token.  This function will use the config function
+ * osapi.container.ContainerConfig.GET_CONTAINER_TOKEN to fetch a container token, if needed,
+ * unless the token is specified in the optional parameter, in which case the token will be
+ * updated with the provided value immediately.
+ *
+ * @param {function=} callback Function to run when container token is valid.
+ * @param {String=} token The containers new security token.
+ * @param {number=} ttl The token's ttl in seconds. If token is specified and ttl is 0,
+ *   token refresh will be disabled.
+ * @see osapi.container.ContainerConfig.GET_CONTAINER_TOKEN (constants.js)
+ */
+osapi.container.Container.prototype.updateContainerSecurityToken = function(callback, token, ttl) {
+  this.service_.updateContainerSecurityToken(callback, token, ttl);
+}
+
+/**
  * Start to schedule refreshing of tokens.
  * @param {number} Encountered token time to live in seconds.
  * @private
@@ -633,12 +534,14 @@ osapi.container.Container.prototype.sche
       oldInterval = this.tokenRefreshInterval_,
       newInterval = tokenTTL ? this.setRefreshTokenInterval_(tokenTTL * 1000) : oldInterval,
       refresh = function() {
-        self.lastRefresh_ = osapi.container.util.getCurrentTimeMs();
-        // Schedule the next refresh.
-        self.tokenRefreshTimer_ = setTimeout(refresh, newInterval);
-
-        // Do this last so that if it ever errors, we maintain the refresh schedule.
-        self.refreshTokens_();
+        self.updateContainerSecurityToken(function() {
+          self.lastRefresh_ = osapi.container.util.getCurrentTimeMs();
+          // Schedule the next refresh.
+          self.tokenRefreshTimer_ = setTimeout(refresh, newInterval);
+
+          // Do this last so that if it ever errors, we maintain the refresh schedule.
+          self.refreshTokens_();
+        });
       };
 
   // If enabled, check to see if we no schedule or if the two intervals are different and update the schedule.
@@ -675,7 +578,7 @@ osapi.container.Container.prototype.unsc
   if (this.tokenRefreshTimer_) {
     var urls = this.getTokenRefreshableGadgetUrls_();
     if (urls.length <= 0) {
-      window.clearInterval(this.tokenRefreshTimer_);
+      clearTimeout(this.tokenRefreshTimer_);
       this.tokenRefreshTimer_ = null;
     }
   }
@@ -809,14 +712,47 @@ osapi.container.Container.prototype.addP
  * @return {Array} An array of URLs of gadgets.
  * @private
  */
+// TODO: this function needs to be renamed, perhaps: getTokenRequestUrls_
 osapi.container.Container.prototype.getTokenRefreshableGadgetUrls_ = function() {
   var result = {};
+
   for (var url in this.getActiveGadgetUrls_()) {
     var metadata = this.service_.getCachedGadgetMetadata(url);
     if (metadata[osapi.container.MetadataResponse.NEEDS_TOKEN_REFRESH]) {
-      result[url] = null;
+      result[url] = 1;
     }
   }
+
+  /* Now add all gadget site urls that have moduleIds
+   *
+   * We're basically refreshing a security token for any given
+   * non-persisted (no moduleId) navigated or preloaded gadget instance, as
+   * well as each persisted instance (each moduleId) for any given gadgetUrl.
+   *
+   * In other words, if we've got a gadget preloaded or navigated:
+   *   http://foo.com/gadget.xml
+   * We will refresh a token for non-persisted (no moduleId) instances of that
+   * gadget.
+   * If we've got a navigated persisted gadget on the page, we'll refresh that
+   * security token as well.
+   */
+  for (var siteId in this.sites_) {
+    var site = this.sites_[siteId];
+    if (site instanceof osapi.container.GadgetSite) {
+      var holder = site.getActiveGadgetHolder();
+      if (holder) {
+        var url = holder.getUrl();
+            mid = site.getModuleId();
+
+        // If this gadget token does not require refresh
+        // (baseurl is not already in result), don't add it.
+        if (result[url]) {
+          result[osapi.container.util.buildTokenRequestUrl(url, mid)] = 1;
+        }
+      }
+    }
+  }
+
   return osapi.container.util.toArrayOfJsonKeys(result);
 };
 
@@ -843,9 +779,9 @@ osapi.container.Container.prototype.getN
   for (var siteId in this.sites_) {
     var site = this.sites_[siteId];
     if (site instanceof osapi.container.GadgetSite) {
-      var holder = (site.getActiveGadgetHolder || site.getActiveUrlHolder).call(site);
+      var holder = site.getActiveGadgetHolder(site);
       if(holder) {
-        result[holder.getUrl()] = null;
+        result[holder.getUrl()] = 1;
       }
     }
   }
@@ -868,14 +804,18 @@ osapi.container.Container.prototype.refr
     // update pre-loaded gadgets, since new tokens will take effect when they
     // are navigated to, from cache.
     for (var siteId in self.sites_) {
-      if (self.sites_[siteId] instanceof osapi.container.GadgetSite) {
-        var holder = self.sites_[siteId].getActiveGadgetHolder();
+      var site = self.sites_[siteId];
+      if (site instanceof osapi.container.GadgetSite) {
+        var holder = site.getActiveGadgetHolder();
         var gadgetInfo = self.service_.getCachedGadgetMetadata(holder.getUrl());
         if (gadgetInfo[osapi.container.MetadataResponse.NEEDS_TOKEN_REFRESH]) {
-          var tokenInfo = response[holder.getUrl()];
+          var mid = site.getModuleId(),
+              url = osapi.container.util.buildTokenRequestUrl(holder.getUrl(), mid),
+              tokenInfo = response[url];
+
           if (tokenInfo.error) {
             gadgets.warn(['Failed to get token for gadget ',
-                holder.getUrl(), '.'].join(''));
+                url, '.'].join(''));
           } else {
             gadgets.rpc.call(holder.getIframeId(), 'update_security_token', null,
                 tokenInfo[osapi.container.TokenResponse.TOKEN]);

Modified: shindig/trunk/features/src/main/javascript/features/container/feature.xml
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container/feature.xml?rev=1231211&r1=1231210&r2=1231211&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container/feature.xml (original)
+++ shindig/trunk/features/src/main/javascript/features/container/feature.xml Fri Jan 13 17:59:32 2012
@@ -46,16 +46,7 @@ under the License.
       <exports type="js">osapi.container.Container.prototype.rpcRegister</exports>
       <exports type="js">osapi.container.Container.prototype.onConstructed</exports>
       <exports type="js">osapi.container.Container.prototype.getGadgetSiteById</exports>
-      <exports type="js">osapi.container.ContainerConfig.ALLOW_DEFAULT_VIEW</exports>
-      <exports type="js">osapi.container.ContainerConfig.RENDER_CAJOLE</exports>
-      <exports type="js">osapi.container.ContainerConfig.RENDER_DEBUG</exports>
-      <exports type="js">osapi.container.ContainerConfig.RENDER_DEBUG_PARAM</exports>
-      <exports type="js">osapi.container.ContainerConfig.RENDER_TEST</exports>
-      <exports type="js">osapi.container.ContainerConfig.TOKEN_REFRESH_INTERVAL</exports>
-      <exports type="js">osapi.container.ContainerConfig.NAVIGATE_CALLBACK</exports>
-      <exports type="js">osapi.container.ContainerConfig.PRELOAD_REF_TIME</exports>
-      <exports type="js">osapi.container.ContainerConfig.PRELOAD_METADATAS</exports>
-      <exports type="js">osapi.container.ContainerConfig.PRELOAD_TOKENS</exports>
+      <exports type="js">osapi.container.Container.prototype.updateContainerSecurityToken</exports>
       <exports type="rpc">resize_iframe</exports>
       <exports type="rpc">resize_iframe_width</exports>
       <exports type="rpc">set_pref</exports>

Modified: shindig/trunk/features/src/main/javascript/features/container/service.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container/service.js?rev=1231211&r1=1231210&r2=1231211&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container/service.js (original)
+++ shindig/trunk/features/src/main/javascript/features/container/service.js Fri Jan 13 17:59:32 2012
@@ -24,11 +24,18 @@
 
 
 /**
- * @param {Object=} opt_config Configuration JSON.
+ * @param {osapi.container.Container} The container that this service services.
  * @constructor
  */
-osapi.container.Service = function(opt_config) {
-  var config = this.config_ = opt_config || {};
+osapi.container.Service = function(container) {
+  /**
+   * The container that this service services.
+   * @type {osapi.container.Container}
+   * @private
+   */
+  this.container_ = container;
+
+  var config = this.config_ = container.config_ || {};
 
   var injectedEndpoint = ((gadgets.config.get('osapi') || {}).endPoints ||
           [window.__API_URI.getOrigin() + '/rpc'])[0];
@@ -118,31 +125,31 @@ osapi.container.Service.prototype.getGad
     request['ids'] = uncachedUrls;
     request['language'] = this.getLanguage();
     request['country'] = this.getCountry();
-    osapi['gadgets']['metadata'](request).execute(function(response) {
-
-      // If response entirely fails, augment individual errors.
-      if (response['error']) {
-        for (var i = 0; i < request['ids'].length; i++) {
-          finalResponse[id] = { 'error' : response['error'] };
+    this.updateContainerSecurityToken(function() {
+      osapi['gadgets']['metadata'](request).execute(function(response) {
+        // If response entirely fails, augment individual errors.
+        if (response['error']) {
+          for (var i = 0; i < request['ids'].length; i++) {
+            finalResponse[id] = { 'error' : response['error'] };
+          }
+
+        // Otherwise, cache response. Augment final response with server response.
+        } else {
+          var currentTimeMs = osapi.container.util.getCurrentTimeMs();
+          for (var id in response) {
+            var resp = response[id];
+            self.updateResponse_(resp, id, currentTimeMs);
+            self.cachedMetadatas_[id] = resp;
+            finalResponse[id] = resp;
+          }
         }
 
-      // Otherwise, cache response. Augment final response with server response.
-      } else {
-        var currentTimeMs = osapi.container.util.getCurrentTimeMs();
-        for (var id in response) {
-          var resp = response[id];
-          self.updateResponse_(resp, id, currentTimeMs);
-          self.cachedMetadatas_[id] = resp;
-          finalResponse[id] = resp;
-        }
-      }
-
-      callback(finalResponse);
+        callback(finalResponse);
+      });
     });
   }
 };
 
-
 /**
  * Add preloaded gadgets to cache
  * @param {Object} response hash of gadgets metadata.
@@ -222,8 +229,11 @@ osapi.container.Service.prototype.getGad
     // Otherwise, cache response. Augment final response with server response.
     } else {
       for (var id in response) {
-        response[id]['url'] = id; // make sure url is set
-        self.cachedTokens_[id] = response[id];
+        var mid = response[osapi.container.TokenResponse.MODULE_ID],
+            url = osapi.container.util.buildTokenRequestUrl(id, mid);
+
+        //response[id]['url'] = id; // make sure url is set
+        self.cachedTokens_[url] = response[id];
         finalResponse[id] = response[id];
       }
     }
@@ -232,11 +242,13 @@ osapi.container.Service.prototype.getGad
   };
 
   // If we have a custom token fetch function, call it -- otherwise use the default
-  if (this.config_[osapi.container.ContainerConfig.GET_GADGET_TOKEN]) {
-    this.config_[osapi.container.ContainerConfig.GET_GADGET_TOKEN](request, tokenResponseCallback);
-  } else {
-    osapi['gadgets']['token'](request).execute(tokenResponseCallback);
-  }
+  self.updateContainerSecurityToken(function() {
+    if (self.config_[osapi.container.ContainerConfig.GET_GADGET_TOKEN]) {
+      self.config_[osapi.container.ContainerConfig.GET_GADGET_TOKEN](request, tokenResponseCallback);
+    } else {
+      osapi['gadgets']['token'](request).execute(tokenResponseCallback);
+    }
+  });
 };
 
 
@@ -388,29 +400,89 @@ osapi.container.Service.prototype.getCou
   }
 };
 
+/**
+ * Container Token Refresh
+ */
+(function() {
+  var containerTimeout, lastRefresh, fetching,
+      containerTokenTTL = 1800000 * 0.8, // 30 min default token ttl
+      callbacks = [];
+
+
+  function runCallbacks(callbacks) {
+    while (callbacks.length) {
+      callbacks.shift().call(null); // Window context
+    }
+  }
 
-// -----------------------------------------------------------------------------
-// Configuration
-// -----------------------------------------------------------------------------
+  function refresh(fetch_once) {
+    fetching = true;
+    if (containerTimeout) {
+      clearTimeout(containerTimeout);
+      containerTimeout = 0;
+    }
 
+    var fetch = fetch_once || this.config_[osapi.container.ContainerConfig.GET_CONTAINER_TOKEN];
+    if (fetch) {
+      var self = this;
+      fetch(function(token, ttl) { // token and ttl may be undefined in the case of an error
+        fetching = false;
+
+        // Use last known ttl if there was an error
+        containerTokenTTL = token ? (ttl * 1000 * 0.8) : containerTokenTTL;
+        if (containerTokenTTL) {
+          // Refresh again in 80% of the reported ttl
+          containerTimeout = setTimeout(gadgets.util.makeClosure(this, refresh), containerTokenTTL);
+        }
 
-/**
- * Enumeration of configuration keys for this service. This is specified in
- * JSON to provide extensible configuration.
- * @enum {string}
- */
-osapi.container.ServiceConfig = {};
+        if (token) {
+          // Looks like everything worked out...  let's update the token.
+          shindig.auth.updateSecurityToken(token);
+          lastRefresh =  osapi.container.util.getCurrentTimeMs();
+          // And then run all the callbacks waiting for this.
+          runCallbacks(callbacks);
+        }
+      });
+    } else {
+      fetching = false;
+      // Fail gracefully, container supplied no fetch function. Do not hold on to callbacks.
+      runCallbacks(callbacks);
+    }
+  }
 
-/**
- * Host to fetch gadget information, via XHR.
- * @type {string}
- * @const
- */
-osapi.container.ServiceConfig.API_HOST = 'apiHost';
+  /**
+   * @see osapi.container.Container.prototype.updateContainerSecurityToken
+   */
+  osapi.container.Service.prototype.updateContainerSecurityToken = function(callback, token, ttl) {
+    var now = osapi.container.util.getCurrentTimeMs(),
+        needsRefresh = containerTokenTTL &&
+            (fetching || token || !lastRefresh || now > lastRefresh + containerTokenTTL);
+    if (needsRefresh) {
+      // Hard expire in 95% of originial ttl.
+      var expired = !lastRefresh || now > lastRefresh + (containerTokenTTL * 95 / 80);
+      if (!expired && callback) {
+        // Token not expired, but needs refresh.  Don't block operations that need a valid token.
+        callback();
+      } else if (callback) {
+        // We have a callback, there's either a fetch happening, or we otherwise need to refresh the
+        // token.  Place it in the callbacks queue to be run after the fetch (currently running or
+        // soon to be launched) completes.
+        callbacks.push(callback);
+      }
 
-/**
- * Path to fetch gadget information, via XHR.
- * @type {string}
- * @const
- */
-osapi.container.ServiceConfig.API_PATH = 'apiPath';
+      if (token) {
+        // We are trying to set a token initially.  Run refresh with a fetch_once function that simply
+        // returns the canned values.  Then schedule the refresh using the function in the config
+        refresh.call(this, function(result) {
+          result(token, ttl);
+        });
+      } else if (!fetching) {
+        // There's no fetch going on right now. We need to start one because the token needs a refresh
+        refresh.call(this);
+      }
+    } else if (callback) {
+      // No refresh needed, run the callback because the token is fine.
+      callback();
+    }
+  };
+})();
\ No newline at end of file

Modified: shindig/trunk/features/src/test/javascript/features/container/gadget_holder_test.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/test/javascript/features/container/gadget_holder_test.js?rev=1231211&r1=1231210&r2=1231211&view=diff
==============================================================================
--- shindig/trunk/features/src/test/javascript/features/container/gadget_holder_test.js (original)
+++ shindig/trunk/features/src/test/javascript/features/container/gadget_holder_test.js Fri Jan 13 17:59:32 2012
@@ -41,8 +41,14 @@ GadgetHolderTest.prototype.tearDown = fu
 };
 
 GadgetHolderTest.prototype.testNew = function() {
-  var element = {};
-  var holder = new osapi.container.GadgetHolder(123, element);
+  var element = {
+    getAttribute: function() {
+      return '0';
+    },
+    id: '123'
+  };
+  var site = new osapi.container.GadgetSite({gadgetEl: element});
+  var holder = new osapi.container.GadgetHolder(site, element);
   this.assertEquals(element, holder.getElement());
   this.assertNull(holder.getIframeId());
   this.assertNull(holder.getGadgetInfo());
@@ -56,7 +62,11 @@ GadgetHolderTest.prototype.testRenderWit
       'url' : 'gadget.xml'
   };
   this.setupGadgetsRpcSetupReceiver();
-  var holder = new osapi.container.GadgetHolder(123, element, '__gadgetOnLoad');
+  var element = {
+    id: '123'
+  };
+  var site = new osapi.container.GadgetSite({gadgetEl: element});
+  var holder = new osapi.container.GadgetHolder(site, element, '__gadgetOnLoad');
   holder.render(gadgetInfo, {}, {});
   this.assertEquals('<iframe' +
       ' marginwidth="0"' +
@@ -69,13 +79,12 @@ GadgetHolderTest.prototype.testRenderWit
       ' id="__gadget_123"' +
       ' name="__gadget_123"' +
       ' src="http://shindig/gadgets/ifr?url=gadget.xml&debug=0&nocache=0&testmode=0' +
-          '&parent=http%3A//container.com&mid=123"' +
+          '&parent=http%3A//container.com&mid=0"' +
       ' ></iframe>',
       element.innerHTML);
 };
 
 GadgetHolderTest.prototype.testRenderWithRenderRequests = function() {
-  var element = {};
   var gadgetInfo = {
       'iframeUrl' : 'http://shindig/gadgets/ifr?url=gadget.xml',
       'url' : 'gadget.xml'
@@ -90,7 +99,11 @@ GadgetHolderTest.prototype.testRenderWit
       'width' : 222
   };
   this.setupGadgetsRpcSetupReceiver();
-  var holder = new osapi.container.GadgetHolder(123, element, '__gadgetOnLoad');
+  var element = {
+    id: '123'
+  };
+  var site = new osapi.container.GadgetSite({gadgetEl: element, moduleId: 123});
+  var holder = new osapi.container.GadgetHolder(site, element, '__gadgetOnLoad');
   holder.render(gadgetInfo, {}, renderParams);
   this.assertEquals('<iframe' +
       ' marginwidth="0"' +
@@ -106,7 +119,7 @@ GadgetHolderTest.prototype.testRenderWit
       ' width="222"' +
       ' name="__gadget_123"' +
       ' src="http://shindig/gadgets/ifr?url=gadget.xml&debug=1&nocache=1&testmode=1' +
-          '&libs=caja&caja=1&parent=http%3A//container.com&mid=123"' +
+          '&libs=caja&caja=1&parent=http%3A//container.com&mid=0"' +
       ' ></iframe>',
       element.innerHTML);
 };

Modified: shindig/trunk/features/src/test/javascript/features/container/service_test.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/test/javascript/features/container/service_test.js?rev=1231211&r1=1231210&r2=1231211&view=diff
==============================================================================
--- shindig/trunk/features/src/test/javascript/features/container/service_test.js (original)
+++ shindig/trunk/features/src/test/javascript/features/container/service_test.js Fri Jan 13 17:59:32 2012
@@ -32,9 +32,15 @@ ServiceTest.inherits(TestCase);
 ServiceTest.prototype.setUp = function() {
   this.apiUri = window.__API_URI;
   window.__API_URI = shindig.uri('http://shindig.com');
-  this.container = window.__CONTAINER;
-  window.__CONTAINER = "best_container";
+  this.containerUri = window.__CONTAINER_URI;
+  window.__CONTAINER_URI = shindig.uri('http://container.com');
   this.osapiGadgets = osapi.gadgets;
+  this.shindigContainerGadgetSite = osapi.container.GadgetSite;
+  this.gadgetsRpc = gadgets.rpc;
+  gadgets.rpc = {
+    register: function() {
+    }
+  };
   
   this.self = {};
   var response = {};
@@ -43,7 +49,9 @@ ServiceTest.prototype.setUp = function()
 
 ServiceTest.prototype.tearDown = function() {
   window.__API_URI = this.apiUri;
-  window.__CONTAINER = this.container;
+  window.__CONTAINER_URI = this.containerUri;
+  osapi.container.GadgetSite = this.shindigContainerGadgetSite;
+  gadgets.rpc = this.gadgetsRpc;
   osapi.gadgets = this.osapiGadgets;
 };
 
@@ -65,14 +73,14 @@ ServiceTest.prototype.setupUtilCurrentTi
 };
 
 ServiceTest.prototype.testGetGadgetMetadata = function() {
-  var service = new osapi.container.Service({
+  var service = new osapi.container.Service(new osapi.container.Container({
     GET_LANGUAGE: function() {
       return 'pt'; 
     },
     GET_COUNTRY: function() {
       return 'BR';
     }
-  });
+  }));
   service.cachedMetadatas_ = {
     'cached1.xml' : {
       'url' : 'cached1.xml',
@@ -143,7 +151,7 @@ ServiceTest.prototype.testGetGadgetMetad
 };
 
 ServiceTest.prototype.testUncacheStaleGadgetMetadataExcept = function() {
-  var service = new osapi.container.Service();
+  var service = new osapi.container.Service(new osapi.container.Container());
   service.cachedMetadatas_ = {
       'cached1.xml' : { 'localExpireTimeMs' : 100 },
       'cached2.xml' : { 'localExpireTimeMs' : 200 },
@@ -162,7 +170,7 @@ ServiceTest.prototype.testUncacheStaleGa
 };
 
 ServiceTest.prototype.testUpdateResponse = function() {
-  var service = new osapi.container.Service();
+  var service = new osapi.container.Service(new osapi.container.Container());
   this.setupUtilCurrentTimeMs(120);
 
   var data = {responseTimeMs : 100, expireTimeMs : 105};
@@ -177,7 +185,7 @@ ServiceTest.prototype.testUpdateResponse
 };
 
 ServiceTest.prototype.testAddToCache = function() {
-  var service = new osapi.container.Service();
+  var service = new osapi.container.Service(new osapi.container.Container());
   this.setupUtilCurrentTimeMs(120);
 
   var cache = {};

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandler.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandler.java?rev=1231211&r1=1231210&r2=1231211&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandler.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandler.java Fri Jan 13 17:59:32 2012
@@ -18,12 +18,19 @@
  */
 package org.apache.shindig.gadgets.servlet;
 
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.inject.Inject;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionService;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorCompletionService;
+import java.util.concurrent.ExecutorService;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.http.HttpServletResponse;
 
 import org.apache.shindig.common.uri.Uri;
 import org.apache.shindig.common.uri.Uri.UriException;
@@ -40,20 +47,12 @@ import org.apache.shindig.protocol.Servi
 import org.apache.shindig.protocol.conversion.BeanDelegator;
 import org.apache.shindig.protocol.conversion.BeanFilter;
 
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CompletionService;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorCompletionService;
-import java.util.concurrent.ExecutorService;
-
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import javax.servlet.http.HttpServletResponse;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Inject;
 
 /**
  * Provides endpoints for gadget metadata lookup and more.
@@ -129,7 +128,8 @@ public class GadgetsHandler {
   protected final BeanDelegator beanDelegator;
 
   @Inject
-  public GadgetsHandler(ExecutorService executor, GadgetsHandlerService handlerService,
+  public GadgetsHandler(ExecutorService executor,
+                        GadgetsHandlerService handlerService,
                         BeanFilter beanFilter) {
     this.executor = executor;
     this.handlerService = handlerService;
@@ -317,7 +317,7 @@ public class GadgetsHandler {
   protected Callable<CallableData> createTokenJob(final String url,
       BaseRequestItem request) throws ProcessingException {
     // TODO: Get token duration from requests
-    final TokenRequestData tokenRequest = new TokenRequestData(url, request, null);
+    final TokenRequestData tokenRequest = new TokenRequestData(url, request);
     return new Callable<CallableData>() {
       public CallableData call() throws Exception {
         try {
@@ -563,15 +563,27 @@ public class GadgetsHandler {
   protected class TokenRequestData extends AbstractRequest
       implements GadgetsHandlerApi.TokenRequest {
 
-    public TokenRequestData(String url, BaseRequestItem request, Long durationSeconds)
+    private Long moduleId;
+
+    public TokenRequestData(String url, BaseRequestItem request)
         throws ProcessingException {
       super(url, request, DEFAULT_TOKEN_FIELDS);
+
+      // The moduleId for the gadget (if it exists) is the fragment of the URI:
+      //  ex: http://example.com/gadget.xml#1 or http://example.com/gadget.xml
+      // zero is implied if missing.
+      String moduleId = this.uri.getFragmentParameter("moduleId");
+      this.moduleId = moduleId == null ? 0 : Long.valueOf(moduleId);
     }
 
     public GadgetsHandlerApi.AuthContext getAuthContext() {
       return beanDelegator.createDelegator(
           request.getToken(), GadgetsHandlerApi.AuthContext.class);
     }
+
+    public Long getModuleId() {
+      return moduleId;
+    }
   }
 
   @VisibleForTesting

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java?rev=1231211&r1=1231210&r2=1231211&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java Fri Jan 13 17:59:32 2012
@@ -23,6 +23,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.shindig.auth.SecurityToken;
 import org.apache.shindig.common.uri.Uri;
 import org.apache.shindig.protocol.conversion.BeanFilter.Unfiltered;
 
@@ -198,12 +199,13 @@ public class GadgetsHandlerApi {
 
   public interface TokenRequest extends BaseRequest {
     public AuthContext getAuthContext();
-    // TODO: Consider support container controlled token duration
-    // public Long getDurationSeconds();
+    public Long getModuleId();
   }
 
   public interface TokenResponse extends BaseResponse {
     public String getToken();
+    public Integer getTokenTTL();
+    public Long getModuleId();
   }
 
   // TODO(jasvir): Support getRefresh and noCache

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerService.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerService.java?rev=1231211&r1=1231210&r2=1231211&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerService.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerService.java Fri Jan 13 17:59:32 2012
@@ -55,6 +55,7 @@ import org.apache.shindig.gadgets.js.JsS
 import org.apache.shindig.gadgets.process.ProcessingException;
 import org.apache.shindig.gadgets.process.Processor;
 import org.apache.shindig.gadgets.servlet.CajaContentRewriter.CajoledResult;
+import org.apache.shindig.gadgets.servlet.GadgetsHandlerApi.AuthContext;
 import org.apache.shindig.gadgets.spec.Feature;
 import org.apache.shindig.gadgets.spec.GadgetSpec;
 import org.apache.shindig.gadgets.spec.LinkSpec;
@@ -152,6 +153,7 @@ public class GadgetsHandlerService {
   protected final CajaContentRewriter cajaContentRewriter;
   protected final GadgetAdminStore gadgetAdminStore;
   protected final FeatureRegistryProvider featureRegistryProvider;
+  protected final ModuleIdManager moduleIdManager;
 
   @Inject
   public GadgetsHandlerService(TimeSource timeSource, Processor processor,
@@ -161,7 +163,8 @@ public class GadgetsHandlerService {
       @Named("shindig.cache.xml.refreshInterval") long specRefreshInterval,
       BeanFilter beanFilter, CajaContentRewriter cajaContentRewriter,
       GadgetAdminStore gadgetAdminStore,
-      FeatureRegistryProvider featureRegistryProvider) {
+      FeatureRegistryProvider featureRegistryProvider,
+      ModuleIdManager moduleIdManager) {
     this.timeSource = timeSource;
     this.processor = processor;
     this.iframeUriManager = iframeUriManager;
@@ -176,6 +179,7 @@ public class GadgetsHandlerService {
     this.cajaContentRewriter = cajaContentRewriter;
     this.gadgetAdminStore = gadgetAdminStore;
     this.featureRegistryProvider = featureRegistryProvider;
+    this.moduleIdManager = moduleIdManager;
 
     this.beanDelegator = new BeanDelegator(API_CLASSES, ENUM_CONVERSION_MAP);
   }
@@ -265,12 +269,36 @@ public class GadgetsHandlerService {
       throws SecurityTokenException, ProcessingException {
     verifyBaseParams(request, true);
     Set<String> fields = beanFilter.processBeanFields(request.getFields());
+    AuthContext authContext = request.getAuthContext();
+
+    SecurityToken tokenData = null;
+    String token = null;
+
+    Long moduleId = request.getModuleId();
+    if (moduleId == null) {
+      // Zero means there's no persisted module instance and the container doesn't care to persist it.
+      moduleId = 0L;
+    } else if (moduleId < 0) {
+      // Please generate a module Id for me
+      moduleId = moduleIdManager.generate(request.getUrl(), authContext);
+    }
+    if (moduleId > 0) {
+      moduleId = moduleIdManager.validate(request.getUrl(), authContext, moduleId);
+    }
+
+    if (moduleId != null) {
+      tokenData = convertAuthContext(authContext, request.getContainer(),
+          request.getUrl().toString(), moduleId);
+      token = securityTokenCodec.encodeToken(tokenData);
+    }
 
-    SecurityToken tokenData = convertAuthContext(request.getAuthContext(), request.getContainer(),
-        request.getUrl().toString());
-    String token = securityTokenCodec.encodeToken(tokenData);
     Long expiryTimeMs = tokenData == null ? null : tokenData.getExpiresAt();
-    return createTokenResponse(request.getUrl(), token, fields, expiryTimeMs);
+
+    Integer tokenTTL = isFieldIncluded(fields, "tokenTTL") ?
+        securityTokenCodec.getTokenTimeToLive() : null;
+    moduleId = isFieldIncluded(fields, "moduleId") ? moduleId : null;
+
+    return createTokenResponse(request.getUrl(), token, fields, expiryTimeMs, tokenTTL, moduleId);
   }
 
   public GadgetsHandlerApi.JsResponse getJs(GadgetsHandlerApi.JsRequest request)
@@ -495,13 +523,18 @@ public class GadgetsHandlerService {
   }
 
   private SecurityToken convertAuthContext(GadgetsHandlerApi.AuthContext authContext,
-      String container, String url) {
+    String container, String url) {
+    return convertAuthContext(authContext, container, url, 0);
+  }
+
+  private SecurityToken convertAuthContext(GadgetsHandlerApi.AuthContext authContext,
+    String container, String url, long moduleId) {
     if (authContext == null) {
       return null;
     }
     return beanDelegator.createDelegator(authContext, SecurityToken.class,
         ImmutableMap.<String, Object>of("container", container,
-            "appid", url, "appurl", url));
+            "appid", url, "appurl", url, "moduleId", moduleId));
   }
 
   public GadgetsHandlerApi.BaseResponse createErrorResponse(
@@ -545,16 +578,21 @@ public class GadgetsHandlerService {
 
   @VisibleForTesting
   GadgetsHandlerApi.TokenResponse createTokenResponse(
-      Uri url, String token, Set<String> fields, Long tokenExpire) {
+      Uri url, String token, Set<String> fields, Long tokenExpire, Integer tokenTTL, Long moduleId) {
     return (GadgetsHandlerApi.TokenResponse) beanFilter.createFilteredBean(
         beanDelegator.createDelegator(null, GadgetsHandlerApi.TokenResponse.class,
-            ImmutableMap.<String, Object>of(
-                "url", url,
-                "error", BeanDelegator.NULL,
-                "token", BeanDelegator.nullable(token),
-                "responsetimems", timeSource.currentTimeMillis(),
-                "expiretimems", BeanDelegator.nullable(tokenExpire))),
-        fields);
+            ImmutableMap.<String, Object>builder()
+                .put("url", url)
+                .put("error", BeanDelegator.NULL)
+                .put("token", BeanDelegator.nullable(token))
+                .put("responsetimems", timeSource.currentTimeMillis())
+                .put("expiretimems", BeanDelegator.nullable(tokenExpire))
+                .put("tokenttl", BeanDelegator.nullable(tokenTTL))
+                .put("moduleid", BeanDelegator.nullable(moduleId))
+            .build()
+        ),
+        fields
+    );
   }
 
   protected JsUri createJsUri(GadgetsHandlerApi.JsRequest request) {

Added: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleIdManager.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleIdManager.java?rev=1231211&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleIdManager.java (added)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleIdManager.java Fri Jan 13 17:59:32 2012
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shindig.gadgets.servlet;
+
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.gadgets.servlet.GadgetsHandlerApi.AuthContext;
+
+import com.google.inject.ImplementedBy;
+
+@ImplementedBy (ModuleIdManagerImpl.class)
+public interface ModuleIdManager {
+  /**
+   * Checks to make sure that the proposed moduleId for this gadget is valid.
+   * This data is not 100% trustworthy becaue we can't extract it from a
+   * token, so we validate it here, usually against the AuthContext viewerId,
+   * gadgetUrl, moduleId combination.
+   *
+   * If the moduleId is invalid the implementation may return:
+   *   null (in which case a null security token will be returned to the container)
+   *   0 (Default value for non-persisted gadgets)
+   *   A newly generated moduleId
+   *
+   * If the supplied moduleId is valid, this function is expected to return the
+   * value of the moduleId param.
+   *
+   * @param gadgetUri The location of the gadget xml to validate the token for
+   * @param containerAuthContext The Auth context.  Basically, the container security token.
+   * @param moduleId The moduleId sent by the container page.
+   * @return moduleId.
+   */
+  public Long validate(Uri gadgetUri, AuthContext containerAuthContext, Long moduleId);
+
+  /**
+   * Generate and persist a new moduleId for the given gadgetUri and container auth context.
+   *
+   * @param gadgetUri The location of the gadget xml to generate the token for
+   * @param containerAuthContext The Auth context.  Basically, the container security token.
+   * @return moduleId.
+   */
+  public Long generate(Uri gadgetUri, AuthContext containerAuthContext);
+}
\ No newline at end of file

Propchange: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleIdManager.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleIdManagerImpl.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleIdManagerImpl.java?rev=1231211&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleIdManagerImpl.java (added)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleIdManagerImpl.java Fri Jan 13 17:59:32 2012
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shindig.gadgets.servlet;
+
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.gadgets.servlet.GadgetsHandlerApi.AuthContext;
+
+/**
+ * Override this class to provide meaningful moduleId validation and generation for gadgets.
+ */
+public class ModuleIdManagerImpl implements ModuleIdManager {
+  public Long validate(Uri gadgetUri, AuthContext containerAuthContext, Long moduleId) {
+    return 0L;
+  }
+
+  public Long generate(Uri gadgetUri, AuthContext containerAuthContext) {
+    return 0L;
+  }
+}
+

Propchange: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleIdManagerImpl.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerServiceTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerServiceTest.java?rev=1231211&r1=1231210&r2=1231211&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerServiceTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerServiceTest.java Fri Jan 13 17:59:32 2012
@@ -129,11 +129,11 @@ public class GadgetsHandlerServiceTest e
     gadgetHandler = new GadgetsHandlerService(timeSource, processor, urlGenerator, tokenCodec,
             proxyUriManager, jsUriManager, proxyHandler, jsPipeline, jsRequestBuilder,
             SPEC_REFRESH_INTERVAL_MS, new BeanFilter(), cajaContentRewriter, gadgetAdminStore,
-            featureRegistryProvider);
+            featureRegistryProvider, new ModuleIdManagerImpl());
     gadgetHandlerWithAdmin = new GadgetsHandlerService(timeSource, processor, urlGenerator,
             tokenCodec, proxyUriManager, jsUriManager, proxyHandler, jsPipeline, jsRequestBuilder,
             SPEC_REFRESH_INTERVAL_MS, new BeanFilter(), cajaContentRewriter, gadgetAdminStore,
-            featureRegistryProvider);
+            featureRegistryProvider, new ModuleIdManagerImpl());
   }
 
   // Next test verify that the API data classes are configured correctly.

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java?rev=1231211&r1=1231210&r2=1231211&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java Fri Jan 13 17:59:32 2012
@@ -138,7 +138,7 @@ public class GadgetsHandlerTest extends 
     GadgetsHandlerService service = new GadgetsHandlerService(timeSource, processor, urlGenerator,
             codec, proxyUriManager, jsUriManager, proxyHandler, jsPipeline, jsRequestBuilder,
             SPEC_REFRESH_INTERVAL, beanFilter, cajaContentRewriter, gadgetAdminStore,
-            featureRegistryProvider);
+            featureRegistryProvider, new ModuleIdManagerImpl());
     GadgetsHandler handler = new GadgetsHandler(new ImmediateExecutorService(), service, beanFilter);
     registry = new DefaultHandlerRegistry(injector, converter,
             new HandlerExecutionListener.NoOpHandler());