Author: mhermanto Date: Tue Sep 21 00:57:03 2010 New Revision: 999173 URL: http://svn.apache.org/viewvc?rev=999173&view=rev Log: Common container updates: - Push down caching mechanism to service for gadget metadata. - Now builds full response from partial request/response. - Allow defaultable view (ie: if requested view does not exist, fallback to view=default). - More friendly runtime error checking. - Per-gadget error reporting.
http://codereview.appspot.com/2226041/ 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/gadget_holder.js shindig/trunk/features/src/main/javascript/features/container/gadget_site.js shindig/trunk/features/src/main/javascript/features/container/service.js shindig/trunk/features/src/main/javascript/features/container/util.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=999173&r1=999172&r2=999173&view=diff ============================================================================== --- shindig/trunk/features/src/main/javascript/features/container/constant.js (original) +++ shindig/trunk/features/src/main/javascript/features/container/constant.js Tue Sep 21 00:57:03 2010 @@ -58,6 +58,7 @@ shindig.container.TokenResponse.TOKEN = * @enum {string} */ shindig.container.RenderParam = {}; +shindig.container.RenderParam.ALLOW_DEFAULT_VIEW = 'allowDefaultView'; shindig.container.RenderParam.CLASS = 'class'; shindig.container.RenderParam.DEBUG = 'debug'; shindig.container.RenderParam.HEIGHT = 'height'; @@ -67,6 +68,7 @@ shindig.container.RenderParam.USER_PREFS shindig.container.RenderParam.VIEW = 'view'; shindig.container.RenderParam.WIDTH = 'width'; + /** * Constants to key into request viewParam JSON. * @enum {string} 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=999173&r1=999172&r2=999173&view=diff ============================================================================== --- shindig/trunk/features/src/main/javascript/features/container/container.js (original) +++ shindig/trunk/features/src/main/javascript/features/container/container.js Tue Sep 21 00:57:03 2010 @@ -44,6 +44,13 @@ shindig.container.Container = function(o this.sites_ = {}; /** + * @type {boolean} + */ + this.allowDefaultView_ = Boolean( + shindig.container.util.getSafeJsonValue(config, + shindig.container.ContainerConfig.ALLOW_DEFAULT_VIEW, true)); + + /** * @type {string} * @private */ @@ -152,12 +159,15 @@ shindig.container.Container.prototype.ge shindig.container.Container.prototype.navigateGadget = function( site, gadgetUrl, viewParams, renderParams, opt_callback) { var callback = opt_callback || function() {}; + if (this.allowDefaultView_) { + renderParams[shindig.container.RenderParam.ALLOW_DEFAULT_VIEW] = true; + } if (this.renderDebug_) { - renderParams['nocache'] = true; - renderParams['debug'] = true; + renderParams[shindig.container.RenderParam.NO_CACHE] = true; + renderParams[shindig.container.RenderParam.DEBUG] = true; } if (this.renderTest_) { - renderParams['testmode'] = true; + renderParams[shindig.container.RenderParam.TEST_MODE] = true; } var self = this; @@ -166,6 +176,10 @@ shindig.container.Container.prototype.na // TODO: Navigate to error screen on primary gadget load failure // TODO: Should display error without doing a standard navigate. // TODO: Bad if the error gadget fails to load. + if (gadgetInfo.error) { + throw [ 'Failed to possibly schedule token refresh for gadget ', + holder.getUrl(), '.' ].join(''); + } if (gadgetInfo[shindig.container.MetadataResponse.NEEDS_TOKEN_REFRESH]) { self.scheduleRefreshTokens_(); } @@ -205,13 +219,14 @@ shindig.container.Container.prototype.pr var request = shindig.container.util.newMetadataRequest(gadgetUrls); var self = this; this.service_.getGadgetMetadata(request, function(response) { - if (!response.error) { - for (var id in response) { - self.addPreloadedGadgetUrl_(id); - if (response[id][shindig.container.MetadatResponse.NEEDS_TOKEN_REFRESH]) { - // Safe to re-schedule many times. - self.scheduleRefreshTokens_(); - } + for (var id in response) { + if (response[id].error) { + throw [ 'Failed to preload gadget ', id, '.' ].join(''); + } + self.addPreloadedGadgetUrl_(id); + if (response[id][shindig.container.MetadatResponse.NEEDS_TOKEN_REFRESH]) { + // Safe to re-schedule many times. + self.scheduleRefreshTokens_(); } } }); @@ -285,6 +300,12 @@ shindig.container.ContainerConfig.TOKEN_ */ shindig.container.ContainerRender = {}; /** + * Allow gadgets to render in unspecified view. + * @type {string} + * @const + */ +shindig.container.ContainerRender.ALLOW_DEFAULT_VIEW = 'allowDefaultView'; +/** * Style class to associate to iframe. * @type {string} * @const @@ -443,17 +464,19 @@ shindig.container.Container.prototype.re var self = this; this.service_.getGadgetToken(request, function(response) { - if (!response.error) { - // Update active token-requiring gadgets with new tokens. Do not need to - // update pre-loaded gadgets, since new tokens will take effect when they - // are navigated to, from cache. - for (var key in self.sites_) { - var holder = self.sites_[key].getActiveGadgetHolder(); - var gadgetInfo = self.service_.getCachedGadgetMetadata(holder.getUrl()); - if (gadgetInfo[shindig.container.MetadataResponse.NEEDS_TOKEN_REFRESH]) { - gadgets.rpc.call(holder.getIframeId(), 'update_security_token', null, - response[holder.getUrl()][shindig.container.TokenResponse.TOKEN]); + // Update active token-requiring gadgets with new tokens. Do not need to + // update pre-loaded gadgets, since new tokens will take effect when they + // are navigated to, from cache. + for (var key in self.sites_) { + var holder = self.sites_[key].getActiveGadgetHolder(); + var gadgetInfo = self.service_.getCachedGadgetMetadata(holder.getUrl()); + if (gadgetInfo[shindig.container.MetadataResponse.NEEDS_TOKEN_REFRESH]) { + var tokenInfo = response[holder.getUrl()]; + if (tokenInfo.error) { + throw [ 'Failed to get token for gadget ', holder.getUrl(), '.' ].join(''); } + gadgets.rpc.call(holder.getIframeId(), 'update_security_token', null, + tokenInfo[shindig.container.TokenResponse.TOKEN]); } } // TODO: Tokens will be stale, but error should not be ignored. Modified: shindig/trunk/features/src/main/javascript/features/container/gadget_holder.js URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container/gadget_holder.js?rev=999173&r1=999172&r2=999173&view=diff ============================================================================== --- shindig/trunk/features/src/main/javascript/features/container/gadget_holder.js (original) +++ shindig/trunk/features/src/main/javascript/features/container/gadget_holder.js Tue Sep 21 00:57:03 2010 @@ -159,9 +159,8 @@ shindig.container.GadgetHolder.prototype /** * Render a gadget into the element. * @param {Object} gadgetInfo the JSON gadget description. - * @param {Object} viewParams View parameters for the gadget. - * @param {Object} renderParams Render parameters for the gadget, including: - * view, width, height. + * @param {Object} viewParams Look at shindig.container.ViewParam. + * @param {Object} renderParams. Look at shindig.container.RenderParam. */ shindig.container.GadgetHolder.prototype.render = function( gadgetInfo, viewParams, renderParams) { @@ -170,10 +169,6 @@ shindig.container.GadgetHolder.prototype this.gadgetInfo_ = gadgetInfo; this.viewParams_ = viewParams; this.renderParams_ = renderParams; - if (!this.gadgetInfo_[shindig.container.MetadataResponse.VIEWS][this.getView()]) { - throw 'View ' + this.view_ + ' unsupported in ' + this.gadgetInfo_['url']; - } - this.el_.innerHTML = this.getIframeHtml_(); // Set up RPC channel. RPC relay url is on gmodules, relative to base of the @@ -266,7 +261,7 @@ shindig.container.GadgetHolder.prototype uri.setFP('mid', String(this.siteId_)); - if (this.hasViewParams_()) { + if (!shindig.container.util.isEmptyJson(this.viewParams_)) { var gadgetParamText = gadgets.json.stringify(this.viewParams_); uri.setFP('view-params', gadgetParamText); } @@ -297,16 +292,3 @@ shindig.container.GadgetHolder.prototype } } }; - - -/** - * Return true if this has view parameters. - * @private - * @return {Boolean} - */ -shindig.container.GadgetHolder.prototype.hasViewParams_ = function() { - for (var key in this.viewParams_) { - return true; - } - return false; -}; Modified: shindig/trunk/features/src/main/javascript/features/container/gadget_site.js URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container/gadget_site.js?rev=999173&r1=999172&r2=999173&view=diff ============================================================================== --- shindig/trunk/features/src/main/javascript/features/container/gadget_site.js (original) +++ shindig/trunk/features/src/main/javascript/features/container/gadget_site.js Tue Sep 21 00:57:03 2010 @@ -194,45 +194,35 @@ shindig.container.GadgetSite.prototype.g /** * Render a gadget in the site, by URI of the gadget XML. * @param {string} gadgetUrl The absolute URL to gadget. - * @param {Object} viewParams View parameters for the gadget. - * @param {Object} renderParams. Render parameters for the gadget, including: - * view, width, height. - * @param {function(Object)=} opt_callback Function called with gadget info after - * navigation has occurred. + * @param {Object} viewParams Look at shindig.container.ViewParam. + * @param {Object} renderParams. Look at shindig.container.RenderParam. + * @param {function(Object)=} opt_callback Function called with gadget info + * after navigation has occurred. */ shindig.container.GadgetSite.prototype.navigateTo = function( gadgetUrl, viewParams, renderParams, opt_callback) { var callback = opt_callback || function() {}; - - // If metadata has been loaded/cached. - var gadgetInfo = this.service_.getCachedGadgetMetadata(gadgetUrl); - if (gadgetInfo) { - this.render(gadgetInfo, viewParams, renderParams); + var request = shindig.container.util.newMetadataRequest([gadgetUrl]); + var self = this; + this.service_.getGadgetMetadata(request, function(response) { + var gadgetInfo = response[gadgetUrl]; + if (gadgetInfo.error) { + var message = [ 'Failed to navigate for gadget ', gadgetUrl, '.' ].join(''); + shindig.container.util.warn(message); + } else { + self.render(gadgetInfo, viewParams, renderParams); + } + // Possibly with an error. Leave to user to deal with raw response. callback(gadgetInfo); - - // Otherwise, fetch gadget metadata. - } else { - var request = shindig.container.util.newMetadataRequest([gadgetUrl]); - var self = this; - this.service_.getGadgetMetadata(request, function(response) { - if (!response.error) { - var gadgetInfo = response[gadgetUrl]; - self.render(gadgetInfo, viewParams, renderParams); - callback(gadgetInfo); - } else { - callback(response); - } - }); - } + }); }; /** * Render a gadget in this site, using a JSON gadget description. * @param {Object} gadgetInfo the JSON gadget description. - * @param {Object} viewParams View parameters for the gadget. - * @param {Object} renderParams. Render parameters for the gadget, including: - * view, width, height. + * @param {Object} viewParams Look at shindig.container.ViewParam. + * @param {Object} renderParams. Look at shindig.container.RenderParam. */ shindig.container.GadgetSite.prototype.render = function( gadgetInfo, viewParams, renderParams) { @@ -243,16 +233,24 @@ shindig.container.GadgetSite.prototype.r previousView = this.currentGadgetHolder_.getView(); } - // Load into the double-buffer if there is one + // Load into the double-buffer if there is one. var el = this.loadingGadgetEl_ || this.currentGadgetEl_; this.loadingGadgetHolder_ = new shindig.container.GadgetHolder(this.id_, el); + // Find requested view. var view = renderParams[shindig.container.RenderParam.VIEW] || viewParams[shindig.container.ViewParam.VIEW] - || previousView - || 'default'; + || previousView; var viewInfo = gadgetInfo[shindig.container.MetadataResponse.VIEWS][view]; + if (!viewInfo && renderParams[shindig.container.RenderParam.ALLOW_DEFAULT_VIEW]) { + view = shindig.container.GadgetSite.DEFAULT_VIEW_; + viewInfo = gadgetInfo[shindig.container.MetadataResponse.VIEWS][view]; + } + if (!viewInfo) { + throw [ 'Unsupported view ', view, ' for gadget ', gadgetInfo_['url'], '.' ].join(''); + } + var delayLoad = this.getFeature('loadstate', gadgetInfo) || this.getFeature('shell', gadgetInfo); @@ -311,8 +309,7 @@ shindig.container.GadgetSite.prototype.r * If token has been fetched at least once, set the token to the most recent * one. Otherwise, leave it. * @param {Object} gadgetInfo The gadgetInfo used to update security token. - * @param {Object} renderParams Render parameters for the gadget, including: - * view, width, and height. + * @param {Object} renderParams. Look at shindig.container.RenderParam. */ shindig.container.GadgetSite.prototype.updateSecurityToken_ = function(gadgetInfo, renderParams) { @@ -429,3 +426,11 @@ shindig.container.GadgetSite.DEFAULT_HEI * @private */ shindig.container.GadgetSite.DEFAULT_WIDTH_ = 320; + + +/** + * Default view of gadget. + * @type {string} + * @private + */ +shindig.container.GadgetSite.DEFAULT_VIEW_ = 'default'; 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=999173&r1=999172&r2=999173&view=diff ============================================================================== --- shindig/trunk/features/src/main/javascript/features/container/service.js (original) +++ shindig/trunk/features/src/main/javascript/features/container/service.js Tue Sep 21 00:57:03 2010 @@ -45,16 +45,6 @@ shindig.container.Service = function(opt shindig.container.ServiceConfig.API_PATH, '/api/rpc/cs')); /** -<<<<<<< HEAD -======= - * @type {boolean} - * @private - */ - this.sameDomain_ = Boolean(shindig.container.util.getSafeJsonValue(config, - shindig.container.ServiceConfig.SAME_DOMAIN, false)); - - /** ->>>>>>> more gjslint cleanups * Map of gadget URLs to cached gadgetInfo response. * @type {Object} * @private @@ -83,31 +73,52 @@ shindig.container.Service.prototype.onCo /** - * Do an immediate fetch of gadgets metadata for gadgets in request.ids, for - * container request.container. The optional callback opt_callback will be + * Return a possibly-cached gadgets metadata for gadgets in request.ids, for + * container request.container. If metadata is not cache, fetch from server + * only for the uncached gadget URLs. The optional callback opt_callback will be * called, after a response is received. * @param {Object} request JSON object representing the request. * @param {function(Object)=} opt_callback function to call upon data receive. */ shindig.container.Service.prototype.getGadgetMetadata = function( request, opt_callback) { + // TODO: come up with an expiration mechanism to evict cached gadgets. + // Can be based on renderParam['nocache']. Be careful with preloaded and + // arbitrarily-navigated gadgets. The former should be indefinite, unless + // unloaded. The later can done without user knowing. var callback = opt_callback || function() {}; - var self = this; - osapi.gadgets.metadata.get(request).execute(function(response) { - if (response.error) { - // Hides internal server error. - callback({ - error: 'Failed to retrieve gadget metadata.', - errorCode: 'NOLOAD' - }); - } else { - for (var id in response) { - var gadgetInfo = response[id]; - self.cachedMetadatas_[id] = gadgetInfo; + var uncachedUrls = this.getUncachedUrls_(request, this.cachedMetadatas_); + var finalResponse = this.getCachedData_(request, this.cachedMetadatas_); + + // If fully cached, return from cache. + if (uncachedUrls.length == 0) { + callback(finalResponse); + + // Otherwise, request for uncached metadatas. + } else { + var self = this; + request = shindig.container.util.newMetadataRequest(uncachedUrls); + osapi.gadgets.metadata.get(request).execute(function(response) { + + // If response entirely fails, augment individual errors. + if (response.error) { + for (var i = 0; i < request.ids.length; i++) { + var id = request.ids[i]; + var message = [ 'Server failure to fetch metadata for gadget ', id, '.' ].join(''); + finalResponse[id] = { error : message }; + } + + // Otherwise, cache response. Augment final response with server response. + } else { + for (var id in response) { + self.cachedMetadatas_[id] = response[id]; + finalResponse[id] = response[id]; + } } - callback(response); - } - }); + + callback(finalResponse); + }); + } }; @@ -118,20 +129,29 @@ shindig.container.Service.prototype.getG shindig.container.Service.prototype.getGadgetToken = function( request, opt_callback) { var callback = opt_callback || function() {}; + + // Do not check against cache. Always do a server fetch. var self = this; osapi.gadgets.token.get(request).execute(function(response) { + var finalResponse = {}; + + // If response entirely fails, augment individual errors. if (response.error) { - // Hides internal server error. - callback({ - error: 'Failed to retrieve gadget token.', - errorCode: 'NOLOAD' - }); + for (var i = 0; i < request.ids.length; i++) { + var id = request.ids[i]; + var message = [ 'Server failure to fetch token for gadget ' + id + '.' ].join(''); + finalResponse[id] = { error : message }; + } + + // Otherwise, cache response. Augment final response with server response. } else { for (var id in response) { self.cachedTokens_[id] = response[id]; + finalResponse[id] = response[id]; } - callback(response); } + + callback(finalResponse); }); }; @@ -175,6 +195,44 @@ shindig.container.Service.prototype.init }; +/** + * Filter cache with requested ids. + * @param {Object} request containing ids. + * @param {Object} cache JSON containing cached data. + * @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; +}; + + +/** + * Extract ids in request not in cache. + * @param {Object} request containing ids. + * @param {Object} cache JSON containing cached data. + * @return {Array.<string>} keys in the json. + * @private + */ +shindig.container.Service.prototype.getUncachedUrls_ = function(request, cache) { + var result = []; + for (var i = 0; i < request.ids.length; i++) { + var id = request.ids[i]; + if (!cache[id]) { + result.push(id); + } + } + return result; +}; + + // ----------------------------------------------------------------------------- // Configuration // ----------------------------------------------------------------------------- @@ -186,6 +244,7 @@ shindig.container.Service.prototype.init * @enum {string} */ shindig.container.ServiceConfig = {}; + /** * Host to fetch gadget information, via XHR. * @type {string} 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=999173&r1=999172&r2=999173&view=diff ============================================================================== --- shindig/trunk/features/src/main/javascript/features/container/util.js (original) +++ shindig/trunk/features/src/main/javascript/features/container/util.js Tue Sep 21 00:57:03 2010 @@ -59,10 +59,6 @@ shindig.container.util.mergeJsons = func }; -shindig.container.util.cloneJson = function(json) { - -}; - /** * 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 @@ -112,3 +108,27 @@ shindig.container.util.toArrayOfJsonKeys } return result; }; + + +/** + * Return true if json is empty. + * @param {Object} json to check. + * @return {Boolean} + */ +shindig.container.util.isEmptyJson = function(json) { + for (var key in json) { + return false; + } + return true; +}; + + +/** + * Put up a warning message to console. + * @param {String} message to warn with. + */ +shindig.container.util.warn = function(message) { + if (console && console.warn) { + console.warn(message); + } +}
