API Docs for:
Show:

File: app/store/endpoints.js

'use strict';

/**
 * Provide the EndpointsController class.
 *
 * @module store
 * @submodule store.endpoints
 */

YUI.add('juju-endpoints-controller', function(Y) {

  var juju = Y.namespace('juju');

  /**
   * This controller manages the endpoints for services, handling the events
   * for the services ModelList and maintaining the endpoints map.
   *
   * EndpointsController({env: Environment, db: Database})
   *
   * @class EndpointsController
   */
  var EndpointsController = Y.Base.create('EndpointsController',
      Y.Base, [], {

        /**
         * Tell `Y.Base` that it should create ad hoc attributes for
         * config properties.
         *
         * @property _allowAdHocAttrs
         * @type {Boolean}
         * @default true
         * @protected
         * @since 3.5.0
         */

        _allowAdHocAttrs: true,

        /**
         * @method initializer
         * @param {Object} cfg Application configuration data.
         */
        initializer: function(cfg) {
          this._subscriptions = [];
          this.endpointsMap = {};
        },

        /**
         * Bind events for endpoint processing.
         *
         * @method bind
         * @return {undefined} Nothing.
         */
        bind: function() {
          var db = this.get('db');

          this._subscriptions.push(
              db.services.after('add', this.serviceAddHandler, this));
          this._subscriptions.push(
              db.services.after('remove', this.serviceRemoveHandler, this));
          this._subscriptions.push(
              db.services.after('*:pendingChange', this.serviceChangeHandler,
              this));
          this._subscriptions.push(
              db.services.after('*:charmChange', this.serviceChangeHandler,
              this));
          this._subscriptions.push(
              db.services.after('reset', this.reset, this));
        },

        /**
         * Unbind events for endpoint processing.
         *
         * @method unbind
         * @return {undefined} Nothing.
         */
        unbind: function() {
          Y.each(this._subscriptions, function(sub) {
            sub.detach();
          });
          this._subscriptions = [];
        },

        /**
         * Destroy this controller
         *
         * @method destructor
         * @return {undefined} Nothing.
         */
        destructor: function() {
          this.unbind();
        },

        /**
         * Reset the endpoints map.
         *
         * @method reset
         * @return {undefined} Nothing.
         */
        reset: function() {
          this.endpointsMap = {};
        },

        /**
         * Setup once('load') handler for a charm.
         *
         * @method setupCharmOnceLoad
         * @param {Object} charm The charm to watch.
         * @param {String} svcName The name of the correpsonding service.
         * @return {undefined} Nothing.
         */
        setupCharmOnceLoad: function(charm, svcName) {
          charm.once('load', Y.bind(function(svcName, evt) {
            this.addServiceToEndpointsMap(svcName, evt.currentTarget);
          }, this, svcName));
        },

        /**
          Generic handler for a service event.  If it is not pending,
          make sure we have a charm.  If the charm has loaded, set or update
          the service's endpoints; otherwise, make that happen once the load
          has completed.

          @method handleServiceEvent
          @param {object} service A service model.
          @return {undefined} Nothing.
         */
        handleServiceEvent: function(service) {
          // If the service is not a ghost (that is, 'pending' is false),
          // process it.
          if (!service.get('pending')) {
            var svcName = service.get('id'),
                db = this.get('db'),
                charm_id = service.get('charm'),
                charm = db.charms.getById(charm_id);
            if (!charm) {
              charm = db.charms.add({id: charm_id})
                .load(this.get('env'),
                  // If views are bound to the charm model, firing "update" is
                  // unnecessary, and potentially even mildly harmful.
                  function(err, result) { db.fire('update'); });
            }
            if (charm.loaded) {
              this.addServiceToEndpointsMap(svcName, charm);
            } else {
              this.setupCharmOnceLoad(charm, svcName);
            }
          }
        },

        /**
         * Handle event for a service being added to the services modellist.
         *
         * @method serviceAddHandler
         * @param {Object} evt The event, containing a model object.
         * @return {undefined} Nothing.
         */
        serviceAddHandler: function(evt) {
          this.handleServiceEvent(evt.model);
        },

        /**
         * Handle event for a service transitioning from a ghost to a corporeal
         * object as indicated by the 'pending' attribute becoming false.  Also
         * handles changes in the service's charm.
         *
         * @method serviceChangeHandler
         * @param {Object} evt The event, containing the service as the target.
         * @return {undefined} Nothing.
         */
        serviceChangeHandler: function(evt) {
          this.handleServiceEvent(evt.target);
        },

        /**
         * Handle event for a service removal.
         *
         * @method serviceRemoveHandler
         * @param {Object} evt The event, containing the service as the target.
         * @return {undefined} Nothing.
         */
        serviceRemoveHandler: function(evt) {
          var svcName = evt.model.get('id');
          delete this.endpointsMap[svcName];
        },

        /**
         * Flatten the relation metadata.
         *
         * @method flatten
         * @param {Object} meta The relation metadata.
         * @return {List} A list of objects, where each entry is a hash with a
         *   'name' key and the key value pairs from the metadata.
         */
        flatten: function(meta) {
          var result = [];
          var rel;
          if (Y.Lang.isValue(meta)) {

            Y.each(meta, function(vo, ko) {
              rel = {};
              rel.name = ko;
              Y.each(vo, function(vi, ki) {
                rel[ki] = vi;
              });
              result.push(rel);
            });
          }
          return result;
        },

        /**
         * Add the service and charm to the endpoints map.
         *
         * @method addServiceToEndpointsMap
         * @param {String} svcName The name of the service.
         * @param {Object} charm The charm for the service.
         * @return {undefined} Nothing.
         */
        addServiceToEndpointsMap: function(svcName, charm) {
          this.endpointsMap[svcName] = {};
          this.endpointsMap[svcName].provides =
              this.flatten(charm.get('provides'));
          this.endpointsMap[svcName].requires =
              this.flatten(charm.get('requires'));
        }
      });

  juju.EndpointsController = EndpointsController;

}, '0.1.0', {
  requires: ['juju-models']
});