atsid/circuits-js

View on GitHub
js/Service.js

Summary

Maintainability
F
3 days
Test Coverage
"use strict";
/**
 * @class {circuits.Service}
 * First class wrapper object for working with services defined in SMDs.
 * In addition to the functions that are defined here, an additional
 * function for each method on the SMD will be added dynamically, which
 * wraps a call to the circuits.ServiceMethod's invoke function. Therefore, any given
 * Service instance can be used to directly invoke the methods defined for the
 * REST service itself.
 */
define([
    "./declare",
    "./ServiceMethod",
    "./PluginMatcher",
    "./util",
    "./log",
    "./plugins/HandlerErrorPlugin",
    "./plugins/HandlerSuccessPlugin",
    "./NativeJsonpDataProvider",
    "./plugins/HandlerTimeoutPlugin"
], function (
    declare,
    ServiceMethod,
    PluginMatcher,
    Util,
    logger,
    HandlerErrorPlugin,
    HandlerSuccessPlugin,
    NativeJsonpDataProvider,
    HandlerTimeoutPlugin
) {

    var util = new Util(),
        module = declare(null, {
            /**
             * @constructor
             * @param reader - Reader instance that has methods for getting RESTful service parameters needed to invoke an HTTP endpoint
             * @param provider - a DataProvider that can take a URL and return data
             * @param plugins - an array of plugins to apply to this service in general See circuits.Plugin.
             */
            constructor: function (reader, provider, factoryPlugins, servicePlugins) {

                var that = this,
                    methodsHash = {},
                    methodsArr = [],
                    schemaId = reader.getSchemaId(),
                    transport = reader.getTransport();

                logger.debug("Creating new Service based on schema: " + schemaId);
                logger.debug("Got service plugins", servicePlugins);
                logger.debug("Got factory plugins", factoryPlugins);
                this.reader = reader;
                this.name = util.serviceName(schemaId);
                this.factoryPlugins = factoryPlugins;
                this.plugins = servicePlugins;
                this.pluginMatcher = new PluginMatcher();

                //if JSONP transport is not supported by the existing provider
                if (transport === 'JSONP' && !provider.supportsTransport('JSONP')) {
                    this.addPlugin({
                        type: 'provider',
                        fn: function () {
                            return new NativeJsonpDataProvider({});
                        }
                    });
                }

                //instantiate and hash ServiceMethods on the Service
                reader.getMethodNames().forEach(function (methodName) {
                    var serviceMethod = new ServiceMethod(methodName, reader, provider);
                    methodsHash[methodName] = serviceMethod;
                    methodsArr.push(serviceMethod);

                    //now add actual functional method directly on the Service for each one defined
                    this[methodName] = function (params, overridePlugins) {

                        var passedPlugins = that.convertCallbackParam(overridePlugins),
                            plugs = that.resolvePlugins(methodName, passedPlugins);

                        return serviceMethod.invoke(params, plugs);
                    };

                }, this);

                /**
                 * Returns a ServiceMethod instance matching the specified name.
                 */
                this.getMethod = function (methodName) {
                    return methodsHash[methodName];
                };

                /**
                 * Converts the passed param object to an array of plugins if necessary.
                 * @param {Object | Array} a set of circuits.Plugin's or an object of the form:
                 * {
                 *  load: {load function, optional}
                 *  error: {error function, optional}
                 *  scope: {Object scope for above functions}
                 * }
                 *
                 * @return {Array} the array of plugins converted if necessary.
                 */
                this.convertCallbackParam = function (param) {
                    var ret = param || [], plugin;

                    if (param && Object.prototype.toString.call(param) !== "[object Array]") {
                        ret = [];
                        if (param.timeout) {
                            logger.debug("Generating handler plugin on timeout (timeout)");
                            plugin = {name: 'generatedHandlerTimeout', fn: param.timeout};
                            plugin.scope = param.scope || plugin;
                            ret.push(new HandlerTimeoutPlugin(plugin));
                        }
                        if (param.load) {
                            logger.debug("Generating handler plugin on load (load)");
                            plugin = {name: 'generatedHandlerSuccess', fn: param.load};
                            plugin.scope = param.scope || plugin;
                            ret.push(new HandlerSuccessPlugin(plugin));
                        }
                        if (param.error) {
                            logger.debug("Generating handler plugin on error (error)");
                            plugin = {name: 'generatedHandlerError', fn: param.error};
                            plugin.scope = param.scope || plugin;
                            ret.push(new HandlerErrorPlugin(plugin));
                        }
                        if (param.onTimeout) {
                            logger.debug("Generating handler plugin on timeout (onTimeout)");
                            plugin = {name: 'generatedHandlerTimeout', fn: param.timeout};
                            plugin.scope = param.scope || plugin;
                            ret.push(new HandlerTimeoutPlugin(plugin));
                        }
                        if (param.onLoad) {
                            logger.debug("Generating handler plugin on load (onLoad)");
                            plugin = {name: 'generatedHandlerSuccess', fn: param.onLoad};
                            plugin.scope = param.scope || plugin;
                            ret.push(new HandlerSuccessPlugin(plugin));
                        }
                        if (param.success) {
                            logger.debug("Generating handler plugin for load (success)");
                            plugin = {name: 'generatedHandlerSuccess', fn: param.success};
                            plugin.scope = param.scope || plugin;
                            ret.push(new HandlerSuccessPlugin(plugin));
                        }
                        if (param.onError) {
                            logger.debug("Generating handler plugin on error (onError)");
                            plugin = {name: 'generatedHandlerError', fn: param.onError};
                            plugin.scope = param.scope || plugin;
                            ret.push(new HandlerErrorPlugin(plugin));
                        }
                        if (param.onProgress) {
                            logger.debug("Generating plugin on load (onLoad)");
                            plugin = {name: 'generatedOnProgress', fn: param.onProgress, type: 'progress'};
                            plugin.scope = param.scope || plugin;
                            ret.push(plugin);
                        }
                        if (param.progress) {
                            logger.debug("Generating plugin on error (onError)");
                            plugin = {name: 'generatedOnProgress', fn: param.progress, type: 'progress'};
                            plugin.scope = param.scope || plugin;
                            ret.push(plugin);
                        }
                    }
                    return ret;
                };

                /**
                 * Returns a list of the ServiceMethods provided by this Service.
                 */
                this.getMethods = function () {
                    return methodsArr;
                };

                logger.debug("Service initialized", this);
            },

            /**
             * Adds a plugin to the list of those applied with this Service.
             */
            addPlugin: function (plugin) {
                this.plugins.push(plugin);
                logger.debug(plugin.name + " has been added");
            },

            /**
             * Deletes a plugin from the Service.
             * TODO: is there a dojo convenience method for this? or an ordered hash set?
             */
            removePlugin: function (plugin) {
                var index, plug, plugs = this.plugins;
                logger.debug("Attempting to remove " + plugin.name);
                for (index = 0; index < plugs.length; index += 1) {
                    plug = plugs[index];
                    if (plug.name === plugin.name && plug.type === plugin.type) {
                        plugs.splice(index, 1);
                        logger.debug(plugin.name + " has been removed");
                        break;
                    }
                }
            },

            /**
             * Resolves any plugins to apply during service processes. As with the callbacks, they are resolved with increasing
             * granularity:
             * 1) The absolute default in the absence of any other plugins is empty arrays for each, meaning none will be executed.
             * 2) If factory plugins for the service factory were supplied, they will be used
             * 3) If service plugins were specified, they will be used.
             * 4) If plugins are supplied at method invocation time, they will be used.
             */
            resolvePlugins: function (methodName, invocationPlugins) {

                logger.debug("Resolving plugins for " + this.name + " " + methodName);
                var plugins = this.pluginMatcher.getPhaseChains(this.name, methodName, this.factoryPlugins, this.plugins, invocationPlugins);
                return plugins;
            }
        });

    return module;
});