| // Copyright 2011 The Closure Library Authors. All Rights Reserved. |
| // |
| // Licensed 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. |
| |
| /** |
| * @fileoverview The central node of a {@link goog.messaging.PortNetwork}. The |
| * operator is responsible for providing the two-way communication channels (via |
| * {@link MessageChannel}s) between each pair of nodes in the network that need |
| * to communicate with one another. Each network should have one and only one |
| * operator. |
| * |
| */ |
| |
| goog.provide('goog.messaging.PortOperator'); |
| |
| goog.require('goog.Disposable'); |
| goog.require('goog.asserts'); |
| goog.require('goog.log'); |
| goog.require('goog.messaging.PortChannel'); |
| goog.require('goog.messaging.PortNetwork'); // interface |
| goog.require('goog.object'); |
| |
| |
| |
| /** |
| * The central node of a PortNetwork. |
| * |
| * @param {string} name The name of this node. |
| * @constructor |
| * @extends {goog.Disposable} |
| * @implements {goog.messaging.PortNetwork} |
| * @final |
| */ |
| goog.messaging.PortOperator = function(name) { |
| goog.messaging.PortOperator.base(this, 'constructor'); |
| |
| /** |
| * The collection of channels for communicating with other contexts in the |
| * network. These are the channels that are returned to the user, as opposed |
| * to the channels used for internal network communication. This is lazily |
| * populated as the user requests communication with other contexts, or other |
| * contexts request communication with the operator. |
| * |
| * @type {!Object<!goog.messaging.PortChannel>} |
| * @private |
| */ |
| this.connections_ = {}; |
| |
| /** |
| * The collection of channels for internal network communication with other |
| * contexts. This is not lazily populated, and always contains entries for |
| * each member of the network. |
| * |
| * @type {!Object<!goog.messaging.MessageChannel>} |
| * @private |
| */ |
| this.switchboard_ = {}; |
| |
| /** |
| * The name of the operator context. |
| * |
| * @type {string} |
| * @private |
| */ |
| this.name_ = name; |
| }; |
| goog.inherits(goog.messaging.PortOperator, goog.Disposable); |
| |
| |
| /** |
| * The logger for PortOperator. |
| * @type {goog.log.Logger} |
| * @private |
| */ |
| goog.messaging.PortOperator.prototype.logger_ = |
| goog.log.getLogger('goog.messaging.PortOperator'); |
| |
| |
| /** @override */ |
| goog.messaging.PortOperator.prototype.dial = function(name) { |
| this.connectSelfToPort_(name); |
| return this.connections_[name]; |
| }; |
| |
| |
| /** |
| * Adds a caller to the network with the given name. This port should have no |
| * services registered on it. It will be disposed along with the PortOperator. |
| * |
| * @param {string} name The name of the port to add. |
| * @param {!goog.messaging.MessageChannel} port The port to add. Must be either |
| * a {@link goog.messaging.PortChannel} or a decorator wrapping a |
| * PortChannel; in particular, it must be able to send and receive |
| * {@link MessagePort}s. |
| */ |
| goog.messaging.PortOperator.prototype.addPort = function(name, port) { |
| this.switchboard_[name] = port; |
| port.registerService(goog.messaging.PortNetwork.REQUEST_CONNECTION_SERVICE, |
| goog.bind(this.requestConnection_, this, name)); |
| }; |
| |
| |
| /** |
| * Connects two contexts by creating a {@link MessageChannel} and sending one |
| * end to one context and the other end to the other. Called when we receive a |
| * request from a caller to connect it to another context (including potentially |
| * the operator). |
| * |
| * @param {string} sourceName The name of the context requesting the connection. |
| * @param {!Object|string} message The name of the context to which |
| * the connection is requested. |
| * @private |
| */ |
| goog.messaging.PortOperator.prototype.requestConnection_ = function( |
| sourceName, message) { |
| var requestedName = /** @type {string} */ (message); |
| if (requestedName == this.name_) { |
| this.connectSelfToPort_(sourceName); |
| return; |
| } |
| |
| var sourceChannel = this.switchboard_[sourceName]; |
| var requestedChannel = this.switchboard_[requestedName]; |
| |
| goog.asserts.assert(goog.isDefAndNotNull(sourceChannel)); |
| if (!requestedChannel) { |
| var err = 'Port "' + sourceName + '" requested a connection to port "' + |
| requestedName + '", which doesn\'t exist'; |
| goog.log.warning(this.logger_, err); |
| sourceChannel.send(goog.messaging.PortNetwork.GRANT_CONNECTION_SERVICE, |
| {'success': false, 'message': err}); |
| return; |
| } |
| |
| var messageChannel = new MessageChannel(); |
| sourceChannel.send(goog.messaging.PortNetwork.GRANT_CONNECTION_SERVICE, { |
| 'success': true, |
| 'name': requestedName, |
| 'port': messageChannel.port1 |
| }); |
| requestedChannel.send(goog.messaging.PortNetwork.GRANT_CONNECTION_SERVICE, { |
| 'success': true, |
| 'name': sourceName, |
| 'port': messageChannel.port2 |
| }); |
| }; |
| |
| |
| /** |
| * Connects together the operator and a caller by creating a |
| * {@link MessageChannel} and sending one end to the remote context. |
| * |
| * @param {string} contextName The name of the context to which to connect the |
| * operator. |
| * @private |
| */ |
| goog.messaging.PortOperator.prototype.connectSelfToPort_ = function( |
| contextName) { |
| if (contextName in this.connections_) { |
| // We've already established a connection with this port. |
| return; |
| } |
| |
| var contextChannel = this.switchboard_[contextName]; |
| if (!contextChannel) { |
| throw Error('Port "' + contextName + '" doesn\'t exist'); |
| } |
| |
| var messageChannel = new MessageChannel(); |
| contextChannel.send(goog.messaging.PortNetwork.GRANT_CONNECTION_SERVICE, { |
| 'success': true, |
| 'name': this.name_, |
| 'port': messageChannel.port1 |
| }); |
| messageChannel.port2.start(); |
| this.connections_[contextName] = |
| new goog.messaging.PortChannel(messageChannel.port2); |
| }; |
| |
| |
| /** @override */ |
| goog.messaging.PortOperator.prototype.disposeInternal = function() { |
| goog.object.forEach(this.switchboard_, goog.dispose); |
| goog.object.forEach(this.connections_, goog.dispose); |
| delete this.switchboard_; |
| delete this.connections_; |
| goog.messaging.PortOperator.base(this, 'disposeInternal'); |
| }; |