Author: mhermanto
Date: Wed Dec 8 01:34:28 2010
New Revision: 1043273
URL: http://svn.apache.org/viewvc?rev=1043273&view=rev
Log:
CC: enable server-driven gadget metadata caching.
http://codereview.appspot.com/3260041/
Modified:
shindig/trunk/features/src/main/javascript/features/container/constant.js
shindig/trunk/features/src/main/javascript/features/container/container.js
shindig/trunk/features/src/main/javascript/features/container/service.js
shindig/trunk/features/src/main/javascript/features/container/util.js
shindig/trunk/features/src/test/javascript/features/container/container_test.js
shindig/trunk/features/src/test/javascript/features/container/service_test.js
Modified:
shindig/trunk/features/src/main/javascript/features/container/constant.js
URL:
http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container/constant.js?rev=1043273&r1=1043272&r2=1043273&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container/constant.js
(original)
+++ shindig/trunk/features/src/main/javascript/features/container/constant.js
Wed Dec 8 01:34:28 2010
@@ -30,6 +30,15 @@ shindig.container = {};
/**
+ * Constants to key into gadget metadata state.
+ * @enum {string}
+ */
+shindig.container.MetadataParam = {};
+shindig.container.MetadataParam.LOCAL_EXPIRE_TIME = 'localExpireTimeMs';
+shindig.container.MetadataParam.URL = 'url';
+
+
+/**
* Constants to key into gadget metadata response JSON.
* @enum {string}
*/
@@ -37,11 +46,13 @@ shindig.container.MetadataResponse = {};
shindig.container.MetadataResponse.IFRAME_URL = 'iframeUrl';
shindig.container.MetadataResponse.NEEDS_TOKEN_REFRESH = 'needsTokenRefresh';
shindig.container.MetadataResponse.VIEWS = 'views';
+shindig.container.MetadataResponse.EXPIRE_TIME_MS = 'expireTimeMs';
shindig.container.MetadataResponse.FEATURES = 'features';
shindig.container.MetadataResponse.HEIGHT = 'height';
shindig.container.MetadataResponse.MODULE_PREFS = 'modulePrefs';
shindig.container.MetadataResponse.PREFERRED_HEIGHT = 'preferredHeight';
shindig.container.MetadataResponse.PREFERRED_WIDTH = 'preferredWidth';
+shindig.container.MetadataResponse.RESPONSE_TIME_MS = 'responseTimeMs';
shindig.container.MetadataResponse.WIDTH = 'width';
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=1043273&r1=1043272&r2=1043273&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container/container.js
(original)
+++ shindig/trunk/features/src/main/javascript/features/container/container.js
Wed Dec 8 01:34:28 2010
@@ -153,6 +153,8 @@ shindig.container.Container.prototype.na
renderParams[shindig.container.RenderParam.TEST_MODE] = true;
}
+ this.refreshService_();
+
var self = this;
// TODO: Lifecycle, add ability for current gadget to cancel nav.
site.navigateTo(gadgetUrl, viewParams, renderParams, function(gadgetInfo) {
@@ -204,6 +206,8 @@ shindig.container.Container.prototype.pr
var callback = opt_callback || function() {};
var request = shindig.container.util.newMetadataRequest(gadgetUrls);
var self = this;
+
+ this.refreshService_();
this.service_.getGadgetMetadata(request, function(response) {
for (var id in response) {
if (response[id].error) {
@@ -221,6 +225,27 @@ shindig.container.Container.prototype.pr
/**
+ * Unload preloaded gadget. Makes future preload request possibly uncached.
+ * @param {string} gadgetUrl gadget URI to unload.
+ */
+shindig.container.Container.prototype.unloadGadget = function(gadgetUrl) {
+ this.unloadGadgets([gadgetUrl]);
+};
+
+
+/**
+ * Unload preloaded gadgets. Makes future preload request possibly uncached.
+ * @param {Array} gadgetUrls gadgets URIs to unload.
+ */
+shindig.container.Container.prototype.unloadGadgets = function(gadgetUrls) {
+ for (var i = 0; i < gadgetUrls.length; i++) {
+ var url = gadgetUrls[i];
+ delete this.preloadedGadgetUrls_[url];
+ }
+};
+
+
+/**
* Fetch the gadget metadata commonly used by container for user preferences.
* @param {string} gadgetUrl gadgets URI to fetch metadata for. to preload.
* @param {function(Object)} callback Function called with gadget metadata.
@@ -228,6 +253,8 @@ shindig.container.Container.prototype.pr
shindig.container.Container.prototype.getGadgetMetadata = function(
gadgetUrl, callback) {
var request = shindig.container.util.newMetadataRequest([gadgetUrl]);
+
+ this.refreshService_();
this.service_.getGadgetMetadata(request, callback);
};
@@ -364,6 +391,18 @@ shindig.container.ContainerRender.WIDTH
/**
+ * Deletes stale cached data in service. The container knows what data are safe
+ * to be marked for deletion.
+ * @private
+ */
+shindig.container.Container.prototype.refreshService_ = function() {
+ var urls = this.getActiveGadgetUrls_();
+ this.service_.uncacheStaleGadgetMetadataExcept(urls);
+ // TODO: also uncache stale gadget tokens.
+};
+
+
+/**
* @param {string} id Iframe ID of gadget holder contained in the gadget site
to get.
* @return {shindig.container.GadgetSite} The gadget site.
* @private
@@ -447,8 +486,7 @@ shindig.container.Container.prototype.re
* @param {string} gadgetUrl URL of preloaded gadget.
* @private
*/
-shindig.container.Container.prototype.addPreloadedGadgetUrl_ = function(
- gadgetUrl) {
+shindig.container.Container.prototype.addPreloadedGadgetUrl_ =
function(gadgetUrl) {
this.preloadedGadgetUrls_[gadgetUrl] = null;
};
@@ -459,36 +497,50 @@ shindig.container.Container.prototype.ad
* @return {Array} An array of URLs of gadgets.
* @private
*/
-shindig.container.Container.prototype.getTokenRefreshableGadgetUrls_ =
- function() {
+shindig.container.Container.prototype.getTokenRefreshableGadgetUrls_ =
function() {
var result = {};
-
- // Collect preloaded gadget urls.
- for (var url in this.preloadedGadgetUrls_) {
+ for (var url in this.getActiveGadgetUrls_()) {
var metadata = this.service_.getCachedGadgetMetadata(url);
if (metadata[shindig.container.MetadataResponse.NEEDS_TOKEN_REFRESH]) {
result[url] = null;
}
}
+ return shindig.container.util.toArrayOfJsonKeys(result);
+};
+
- // Collect active gadget urls.
+/**
+ * Get gadget urls that are either navigated or preloaded.
+ * @return {Object} JSON of gadget URLs.
+ * @private
+ */
+shindig.container.Container.prototype.getActiveGadgetUrls_ = function() {
+ return shindig.container.util.mergeJsons(
+ this.getNavigatedGadgetUrls_(),
+ this.preloadedGadgetUrls_);
+};
+
+
+/**
+ * Get gadget urls that are navigated on page.
+ * @return {Object} JSON of gadget URLs.
+ * @private
+ */
+shindig.container.Container.prototype.getNavigatedGadgetUrls_ = function() {
+ var result = {};
for (var siteId in this.sites_) {
var holder = this.sites_[siteId].getActiveGadgetHolder();
- var url = holder.getUrl();
- var metadata = this.service_.getCachedGadgetMetadata(url);
- if (metadata[shindig.container.MetadataResponse.NEEDS_TOKEN_REFRESH]) {
- result[url] = null;
+ if (holder) {
+ result[holder.getUrl()] = null;
}
}
-
- return shindig.container.util.toArrayOfJsonKeys(result);
+ return result;
};
/**
* Refresh security tokens immediately. This will fetch gadget metadata, along
* with its token and have the token cache updated.
- * @private
*/
shindig.container.Container.prototype.refreshTokens_ = function() {
var ids = this.getTokenRefreshableGadgetUrls_();
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=1043273&r1=1043272&r2=1043273&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container/service.js
(original)
+++ shindig/trunk/features/src/main/javascript/features/container/service.js
Wed Dec 8 01:34:28 2010
@@ -87,8 +87,10 @@ shindig.container.Service.prototype.getG
// arbitrarily-navigated gadgets. The former should be indefinite, unless
// unloaded. The later can done without user knowing.
var callback = opt_callback || function() {};
- var uncachedUrls = this.getUncachedUrls_(request, this.cachedMetadatas_);
- var finalResponse = this.getCachedData_(request, this.cachedMetadatas_);
+
+ var uncachedUrls = shindig.container.util.toArrayOfJsonKeys(
+ this.getUncachedDataByRequest_(this.cachedMetadatas_, request));
+ var finalResponse = this.getCachedDataByRequest_(this.cachedMetadatas_,
request);
// If fully cached, return from cache.
if (uncachedUrls.length == 0) {
@@ -108,10 +110,18 @@ shindig.container.Service.prototype.getG
// Otherwise, cache response. Augment final response with server
response.
} else {
+ var currentTimeMs = shindig.container.util.getCurrentTimeMs();
for (var id in response) {
- response[id]['url'] = id; // make sure url is set
- self.cachedMetadatas_[id] = response[id];
- finalResponse[id] = response[id];
+ var resp = response[id];
+ resp[shindig.container.MetadataParam.URL] = id;
+
+ // This ignores time to fetch metadata. Okay, expect to be < 2s.
+ resp[shindig.container.MetadataParam.LOCAL_EXPIRE_TIME]
+ = resp[shindig.container.MetadataResponse.EXPIRE_TIME_MS]
+ - resp[shindig.container.MetadataResponse.RESPONSE_TIME_MS]
+ + currentTimeMs;
+ self.cachedMetadatas_[id] = resp;
+ finalResponse[id] = resp;
}
}
@@ -173,6 +183,22 @@ shindig.container.Service.prototype.getC
/**
+ * @param {Object} urls JSON containing gadget URLs to avoid removing.
+ */
+shindig.container.Service.prototype.uncacheStaleGadgetMetadataExcept =
function(urls) {
+ for (var url in this.cachedMetadatas_) {
+ if (typeof urls[url] === 'undefined') {
+ var gadgetInfo = this.cachedMetadatas_[url];
+ if (gadgetInfo[shindig.container.MetadataParam.LOCAL_EXPIRE_TIME]
+ < shindig.container.util.getCurrentTimeMs()) {
+ delete this.cachedMetadatas_[url];
+ }
+ }
+ }
+};
+
+
+/**
* Initialize OSAPI endpoint methods/interfaces.
*/
shindig.container.Service.prototype.registerOsapiServices = function() {
@@ -193,37 +219,49 @@ shindig.container.Service.prototype.regi
/**
- * Filter cache with requested ids.
- * @param {Object} request containing ids.
+ * Get cached data by ids listed in request.
* @param {Object} cache JSON containing cached data.
+ * @param {Object} request containing ids.
* @return {Object} JSON containing requested and cached entries.
* @private
*/
-shindig.container.Service.prototype.getCachedData_ = function(request, cache) {
- var result = {};
- for (var i = 0; i < request.ids.length; i++) {
- var id = request.ids[i];
- if (cache[id]) {
- result[id] = cache[id];
- }
- }
- return result;
+shindig.container.Service.prototype.getCachedDataByRequest_ = function(
+ cache, request) {
+ return this.filterCachedDataByRequest_(cache, request,
+ function(data) { return (typeof data !== 'undefined') });
};
/**
- * Extract ids in request not in cache.
+ * Get uncached data by ids listed in request.
+ * @param {Object} cache JSON containing cached data.
* @param {Object} request containing ids.
+ * @return {Object} JSON containing requested and uncached entries.
+ * @private
+ */
+shindig.container.Service.prototype.getUncachedDataByRequest_ = function(
+ cache, request) {
+ return this.filterCachedDataByRequest_(cache, request,
+ function(data) { return (typeof data === 'undefined') });
+};
+
+
+/**
+ * Helper to filter out cached data
* @param {Object} cache JSON containing cached data.
- * @return {Array.<string>} keys in the json.
+ * @param {Object} request containing ids.
+ * @param {Function} filterFunc function to filter result.
+ * @return {Object} JSON containing requested and filtered entries.
* @private
*/
-shindig.container.Service.prototype.getUncachedUrls_ = function(request,
cache) {
- var result = [];
+shindig.container.Service.prototype.filterCachedDataByRequest_ = function(
+ data, request, filterFunc) {
+ var result = {};
for (var i = 0; i < request.ids.length; i++) {
var id = request.ids[i];
- if (!cache[id]) {
- result.push(id);
+ var cachedData = data[id];
+ if (filterFunc(cachedData)) {
+ result[id] = cachedData;
}
}
return result;
Modified: shindig/trunk/features/src/main/javascript/features/container/util.js
URL:
http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container/util.js?rev=1043273&r1=1043272&r2=1043273&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container/util.js
(original)
+++ shindig/trunk/features/src/main/javascript/features/container/util.js Wed
Dec 8 01:34:28 2010
@@ -63,7 +63,7 @@ shindig.container.util.mergeJsons = func
* Construct a JSON request to get gadget metadata. For now, this will request
* a super-set of data needed for all CC APIs requiring gadget metadata, since
* the caching of response is not additive.
- * @param {Array} A list of gadget URLs.
+ * @param {Array} gadgetUrls A list of gadget URLs.
* @return {Object} the resulting JSON.
*/
shindig.container.util.newMetadataRequest = function(gadgetUrls) {
@@ -76,7 +76,9 @@ shindig.container.util.newMetadataReques
'needsTokenRefresh',
'userPrefs.*',
'views.preferredHeight',
- 'views.preferredWidth'
+ 'views.preferredWidth',
+ 'expireTimeMs',
+ 'responseTimeMs'
]
};
};
Modified:
shindig/trunk/features/src/test/javascript/features/container/container_test.js
URL:
http://svn.apache.org/viewvc/shindig/trunk/features/src/test/javascript/features/container/container_test.js?rev=1043273&r1=1043272&r2=1043273&view=diff
==============================================================================
---
shindig/trunk/features/src/test/javascript/features/container/container_test.js
(original)
+++
shindig/trunk/features/src/test/javascript/features/container/container_test.js
Wed Dec 8 01:34:28 2010
@@ -34,13 +34,95 @@ ContainerTest.prototype.setUp = function
window.__API_URI = shindig.uri('http://shindig.com');
this.containerUri = window.__CONTAINER_URI;
window.__CONTAINER_URI = shindig.uri('http://container.com');
+ this.shindigContainerGadgetSite = shindig.container.GadgetSite;
+ this.gadgetsRpc = gadgets.rpc;
};
ContainerTest.prototype.tearDown = function() {
window.__API_URI = this.apiUri;
window.__CONTAINER_URI = this.containerUri;
+ shindig.container.GadgetSite = this.shindigContainerGadgetSite;
+ gadgets.rpc = this.gadgetsRpc;
};
-ContainerTest.prototype.testNew = function() {
- // TODO: here for a placeholder.
+ContainerTest.prototype.testUnloadGadget = function() {
+ this.setupGadgetsRpcRegister();
+ var container = new shindig.container.Container();
+ container.preloadedGadgetUrls_ = {
+ 'preloaded1.xml' : {},
+ 'preloaded2.xml' : {}
+ };
+ container.unloadGadget('preloaded1.xml');
+ this.assertTrue('1', container.preloadedGadgetUrls_['preloaded1.xml'] ==
null);
+ this.assertTrue('2', container.preloadedGadgetUrls_['preloaded2.xml'] !=
null);
+};
+
+ContainerTest.prototype.testUnloadGadgets = function() {
+ this.setupGadgetsRpcRegister();
+ var container = new shindig.container.Container();
+ container.preloadedGadgetUrls_ = {
+ 'preloaded1.xml' : {},
+ 'preloaded2.xml' : {},
+ 'preloaded3.xml' : {}
+ };
+ container.unloadGadgets(['preloaded1.xml', 'preloaded2.xml']);
+ this.assertTrue('1', container.preloadedGadgetUrls_['preloaded1.xml'] ==
null);
+ this.assertTrue('2', container.preloadedGadgetUrls_['preloaded2.xml'] ==
null);
+ this.assertTrue('3', container.preloadedGadgetUrls_['preloaded3.xml'] !=
null);
+};
+
+ContainerTest.prototype.testNavigateGadget = function() {
+ this.setupGadgetsRpcRegister();
+ var container = new shindig.container.Container({
+ 'allowDefaultView' : true,
+ 'renderDebug' : true,
+ 'renderTest' : true
+ });
+
+ this.setupGadgetSite(1, {}, null);
+ var site = container.newGadgetSite(null);
+ container.navigateGadget(site, 'gadget.xml', {}, {});
+ this.assertEquals('gadget.xml', this.site_navigateTo_gadgetUrl);
+ this.assertTrue(this.site_navigateTo_renderParams['allowDefaultView']);
+ this.assertTrue(this.site_navigateTo_renderParams['debug']);
+ this.assertTrue(this.site_navigateTo_renderParams['nocache']);
+ this.assertTrue(this.site_navigateTo_renderParams['testmode']);
+};
+
+ContainerTest.prototype.testNewGadgetSite = function() {
+ this.setupGadgetsRpcRegister();
+ var container = new shindig.container.Container();
+ this.setupGadgetSite(1, {}, null);
+ var site1 = container.newGadgetSite(null);
+ this.setupGadgetSite(2, {}, null);
+ var site2 = container.newGadgetSite(null);
+ this.assertTrue(container.sites_[1] != null);
+ this.assertTrue(container.sites_[2] != null);
+};
+
+ContainerTest.prototype.setupGadgetSite = function(id, gadgetInfo,
gadgetHolder) {
+ var self = this;
+ shindig.container.GadgetSite = function() {
+ return {
+ 'getId' : function() {
+ return id;
+ },
+ 'navigateTo' : function(gadgetUrl, viewParams, renderParams, func) {
+ self.site_navigateTo_gadgetUrl = gadgetUrl;
+ self.site_navigateTo_viewParams = viewParams;
+ self.site_navigateTo_renderParams = renderParams;
+ func(gadgetInfo);
+ },
+ 'getActiveGadgetHolder' : function() {
+ return gadgetHolder;
+ }
+ };
+ };
+};
+
+ContainerTest.prototype.setupGadgetsRpcRegister = function() {
+ gadgets.rpc = {
+ register: function() {
+ }
+ };
};
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=1043273&r1=1043272&r2=1043273&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
Wed Dec 8 01:34:28 2010
@@ -32,13 +32,120 @@ 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.osapiGadgets = osapi.gadgets;
+
+ this.self = {};
+ var response = {};
+ response.error = {};
};
ServiceTest.prototype.tearDown = function() {
window.__API_URI = this.apiUri;
+ window.__CONTAINER = this.container;
+ osapi.gadgets = this.osapiGadgets;
};
-ServiceTest.prototype.testNew = function() {
+ServiceTest.prototype.setupOsapiGadgetsMetadata = function(response) {
+ osapi.gadgets = {};
+ osapi.gadgets.metadata = function(request) {
+ return {
+ execute: function(func) {
+ func(response);
+ }
+ };
+ };
+};
+
+ServiceTest.prototype.setupUtilCurrentTimeMs = function(time) {
+ shindig.container.util.getCurrentTimeMs = function() {
+ return time;
+ };
+};
+
+ServiceTest.prototype.testGetGadgetMetadata = function() {
+ var service = new shindig.container.Service();
+ service.cachedMetadatas_ = {
+ 'cached1.xml' : {
+ 'url' : 'cached1.xml',
+ 'responseTimeMs' : 80,
+ 'expireTimeMs' : 85,
+ 'localExpireTimeMs' : 100
+ }
+ };
+
+ var request = shindig.container.util.newMetadataRequest([
+ 'cached1.xml', 'resp1.xml', 'resp2.xml', 'resp3.xml'
+ ]);
+
+ var response = {
+ 'resp1.xml' : {
+ 'responseTimeMs' : 90,
+ 'expireTimeMs' : 91
+ },
+ 'resp2.xml' : {
+ 'responseTimeMs' : 110,
+ 'expireTimeMs' : 112
+ },
+ 'resp3.xml' : {
+ 'responseTimeMs' : 97,
+ 'expireTimeMs' : 103
+ }
+ };
+
+ var self = this;
+ var callback = function(response) {
+ self.response = response;
+ };
+
+ this.setupUtilCurrentTimeMs(100);
+ this.setupOsapiGadgetsMetadata(response);
+ var metadata = service.getGadgetMetadata(request, callback);
+ var response = self.response;
+
+ this.assertEquals('cached1.xml', response['cached1.xml'].url);
+ this.assertEquals(80, response['cached1.xml'].responseTimeMs);
+ this.assertEquals(85, response['cached1.xml'].expireTimeMs);
+ this.assertEquals(100, response['cached1.xml'].localExpireTimeMs);
+
+ this.assertEquals('resp1.xml', response['resp1.xml'].url);
+ this.assertEquals(90, response['resp1.xml'].responseTimeMs);
+ this.assertEquals(91, response['resp1.xml'].expireTimeMs);
+ this.assertEquals(101, response['resp1.xml'].localExpireTimeMs);
+
+ this.assertEquals('resp2.xml', response['resp2.xml'].url);
+ this.assertEquals(110, response['resp2.xml'].responseTimeMs);
+ this.assertEquals(112, response['resp2.xml'].expireTimeMs);
+ this.assertEquals(102, response['resp2.xml'].localExpireTimeMs);
+
+ this.assertEquals('resp3.xml', response['resp3.xml'].url);
+ this.assertEquals(97, response['resp3.xml'].responseTimeMs);
+ this.assertEquals(103, response['resp3.xml'].expireTimeMs);
+ this.assertEquals(106, response['resp3.xml'].localExpireTimeMs);
+
+ this.assertTrue(service.cachedMetadatas_['cached1.xml'] != null);
+ this.assertTrue(service.cachedMetadatas_['resp1.xml'] != null);
+ this.assertTrue(service.cachedMetadatas_['resp2.xml'] != null);
+ this.assertTrue(service.cachedMetadatas_['resp3.xml'] != null);
+};
+
+ServiceTest.prototype.testUncacheStaleGadgetMetadataExcept = function() {
var service = new shindig.container.Service();
- this.assertTrue(service != null);
+ service.cachedMetadatas_ = {
+ 'cached1.xml' : { 'localExpireTimeMs' : 100 },
+ 'cached2.xml' : { 'localExpireTimeMs' : 200 },
+ 'except1.xml' : { 'localExpireTimeMs' : 100 },
+ 'except2.xml' : { 'localExpireTimeMs' : 200 }
+ };
+ this.setupUtilCurrentTimeMs(150);
+ service.uncacheStaleGadgetMetadataExcept({
+ 'except1.xml' : null,
+ 'except2.xml' : null
+ });
+ this.assertTrue(service.cachedMetadatas_['cached1.xml'] == null);
+ this.assertTrue(service.cachedMetadatas_['cached2.xml'] != null);
+ this.assertTrue(service.cachedMetadatas_['except1.xml'] != null);
+ this.assertTrue(service.cachedMetadatas_['except2.xml'] != null);
};
+