<commit_before>var net = require('net'),
  debug = require('util').debug,
  inspect = require('util').inspect,
  EventEmitter = require("events").EventEmitter,
  BinaryParser = require('./bson/binary_parser').BinaryParser,
  inherits = require('util').inherits,
  Server = require('./connections/server').Server,
  binaryutils = require("./bson/binary_utils");

var Connection = exports.Connection = function(host, port, autoReconnect, options) {
  this.options = options == null ? {} : options;
  this.host = host;
  this.port = port;
  this.autoReconnect = autoReconnect;
  this.drained = true;
  // Fetch the poolsize
  this.poolSize = this.options["poolSize"] == null ? 1 : this.options["poolSize"];
  // Reconnect buffer for messages
  this.messages = [];

  // Status messages
  this.sizeOfMessage = 0;
  this.bytesRead = 0;
  this.buffer = '';
  this.stubBuffer = '';
  this.connected = false;
  this.closedConnectionCount = 0;

  // Connection pool variables
  this.pool = [];
  this.poolByReference = {};
  this.poolIndex = 0;
};

inherits(Connection, EventEmitter);

var getConnection = function(self) {
  return self.pool[self.poolIndex++ % self.pool.length];
}

// Setup the connection pool
var setupConnectionPool = function(self, poolSize, reconnect) {
  // Pool off connections and status variables
  var connectionPool = [];
  var connectedTo = 0;
  var errors = 0;
  var connectionError = null;

  // Receive listener
  var receiveListener = function(result, fd) {
    fd = fd == null ? this.fd : fd;

    // Fetch the pool reference
    var conObj = self.poolByReference[fd];

    // Check if we have an unfinished message
    if(conObj != null && conObj.bytesRead > 0 && conObj.sizeOfMessage > 0) {
      // Calculate remaing bytes to fetch
      var remainingBytes = conObj.sizeOfMessage - conObj.bytesRead;
      // Check if we have multiple packet messages and save the pieces otherwise emit the message
      if(remainingBytes > result.length) {
        // Let's copy all the results into a new buffer
        var buffer = new Buffer(conObj.buffer.length + result.length);
        conObj.buffer.copy(buffer, 0, 0, conObj.buffer.length);
        result.copy(buffer, conObj.buffer.length, 0, result.length);
        // Save the reference to the new buffer
        conObj.buffer = buffer;
        // Adjust the number of read bytes
        conObj.bytesRead = conObj.bytesRead + result.length;
      } else {
        // Cut off the remaining message
        var buffer = new Buffer(conObj.buffer.length + remainingBytes);
        conObj.buffer.copy(buffer, 0, 0, conObj.buffer.length);
        result.copy(buffer, conObj.buffer.length, 0, remainingBytes);
        // Emit the message
        self.emit("data", buffer);
        // Reset the variables
        conObj.buffer = new Buffer(0); conObj.bytesRead = 0; conObj.sizeOfMessage = 0;
        // If message is longer than the current one, keep parsing
        if(remainingBytes < result.length) {
          receiveListener(result.slice(remainingBytes, result.length), fd);
        }
      }
    } else if(conObj != null){
      if(conObj.stubBuffer.length > 0) {
        // Add missing stub files
        var buffer = new Buffer(conObj.stubBuffer.length + result.length);
        conObj.stubBuffer.copy(buffer, 0, 0, conObj.stubBuffer.length);
        result.copy(buffer, conObj.stubBuffer.length, 0, result.length);
        // New result contains the former missing bytes as well as the incoming payload
        result = buffer;
        conObj.stubBuffer = new Buffer(0);
      }

      if(result.length > 4) {
        // var sizeOfMessage = BinaryParser.toInt(result.toString('binary', 0, 4));
        var sizeOfMessage = binaryutils.decodeUInt32(result, 0);
        // We got a partial message, store the result and wait for more
        if(sizeOfMessage > result.length) {
          // Create a new buffer with the correct size
          var buffer = new Buffer(conObj.buffer.length + result.length);
          conObj.buffer.copy(buffer, 0, 0, self.buffer.length);
          result.copy(buffer, conObj.buffer.length, 0, result.length);
          conObj.buffer = buffer;
          // Adjust variables
          conObj.bytesRead = result.length; conObj.sizeOfMessage = sizeOfMessage;
        } else if(sizeOfMessage == result.length) {
          self.emit("data", result);
        } else if(sizeOfMessage < result.length) {
          self.emit("data", result.slice(0, sizeOfMessage));
          receiveListener(result.slice(sizeOfMessage, result.length), fd);
        }
      } else {
        conObj.stubBuffer = result;
      }
    }
  };

  // Fill the pool
  for(var i = 0; i < poolSize; i++) {
    // Create the associated connection
    var connection = net.createConnection(self.port, self.host);
    // Add connnect listener
    connection.addListener("connect", function() {
      // this.setEncoding("binary");
      this.setTimeout(0);
      this.setNoDelay();
      // Update number of connected to server
      connectedTo = connectedTo + 1;
    });

    connection.addListener("error", function(err) {
      // Update number of errors
      errors = errors + 1;
      connectionError = err;
    });

    connection.addListener("timeout", function(err) {
      // Update number of errors
      errors = errors + 1;
      connectionError = err;
    });

    // Add a close listener
    connection.addListener("close", function() {
      // Only emit close once all connections in the pool have closed.
      // Note that connections which emit an error also emit a close.
      if (++self.closedConnectionCount === self.pool.length)
        self.emit("close");
    });

    // Add connection to the pool array
    connectionPool.push({"connection": connection,
      "sizeOfMessage": 0,
      "bytesRead": 0,
      "buffer": new Buffer(0),
      "stubBuffer": ''});

    // Add the listener to the connection
    connection.addListener("data", receiveListener);
  }

  // Function that wait for connection to finish up
  var waitForConnections = function() {
    // Emit a connect message once all connections are up
    if(connectedTo == connectionPool.length) {
      if(reconnect == null || !reconnect) {
        self.connected = true;
        self.poolByReference = {};

         // Save the connections by the fd reference
        self.pool.forEach(function(con) {
          self.poolByReference[con.connection.fd] = con;
        });

        self.emit("connect");
      } else {
        self.connected = false;
        self.emit("reconnect");
      }
    } else if(errors > 0) {
      if(reconnect == null || !reconnect) {
        self.connected = false;
        self.emit("error", connectionError);
      } else {
        self.connected = false;
        self.emit("reconnect");
      }
    } else {
      process.nextTick(waitForConnections);
    }
  }

  // Wait until we are done connected to all pool entries before emitting connect signal
  process.nextTick(waitForConnections);

  // Return the pool
  return connectionPool;
}

// Functions to open the connection
Connection.prototype.open = function() {
  // Create the pool with connections
  this.pool = setupConnectionPool(this, this.poolSize);
}

Connection.prototype.getConnection = function() {
  return getConnection(this);
}

Connection.prototype.close = function(callback) {
  this.connected = false;
  // Close all entries in the pool
  if (!callback) {
    // This seems to block until completion.
    for(var i = 0; i < this.pool.length; i++) {
      this.pool[i].connection.end();
    }
  } else {
    this.once("close", callback);
    this.pool.forEach(function(poolMember, i) {
      var connection = poolMember.connection;
      // Force an async flush.
      connection.write('', function() {
        // Hopefully this doesn't block.
        connection.end();
      });
    });
  }
};

Connection.prototype.send = function(command, rawConnection) {
  var self = this;
  // If we are executing the commnand on the entire pool
  var connection = null;
  // If we are forcing the use of a connection
  if(rawConnection != null) {
    connection = rawConnection;
  } else {
    connection = getConnection(self).connection;
  }

  // Check if the connection is closed
  try {
    if (connection.readyState != "open") {
      throw 'notConnected';
    }

    // Send the command, if it's an array of commands execute them all on the same connection
    if(Array.isArray(command)) {
      for(var i = 0; i < command.length; i++) {
        connection.write(command[i].toBinary());
      }
    } else {
      connection.write(command.toBinary());
    }
  } catch(err) {
    // Check if the connection is closed
    if(connection.readyState != "open" && self.autoReconnect) {
      // Add the message to the queue of messages to send
      self.messages.push(command);
      // Initiate reconnect if no current running
      if(self.currently_reconnecting == null || self.currently_reconnecting == false) {
        self.currently_reconnecting = true;

        // Create the pool with connections
        self.pool = setupConnectionPool(self, self.poolSize, true);
        self.poolByReference = {};
        // Save the connections by the fd reference
        self.pool.forEach(function(con) {
          self.poolByReference[con.connection.fd] = con;
        })

        // Wait for a reconnect and send all the messages
        self.on("resend", function() {
          self.currently_reconnecting = false;
          // Fire the message again
          while(self.messages.length > 0) {
            // Fetch a connection and resend messages
            connection = getConnection(self).connection;
            // Fetch the a message
            var command = self.messages.shift();
            // Fire
            if(Array.isArray(command)) {
              for(var i = 0; i < command.length; i++) {
                connection.write(command[i].toBinary());
              }
            } else {
              connection.write(command.toBinary());
            }
          }
        })
      }
    } else {
      // Set connected to false
      self.connected = false;
      // Throw error
      throw err;
    }
  }
};

/**
* Wrtie command without an attempt of reconnect
* @param command
*/
Connection.prototype.sendwithoutReconnect = function(command) {
  var self = this;
  var connection = this.connection;

  // Check if the connection is closed
  if (connection.readyState != "open") {
    throw new Error( 'Connection closed!' );
  }

  // If connection.write() throws there is
  // no need to reconnect since called by latest master
  // and already went through send() function
  connection.write(command.toBinary(), "binary");
};

// Some basic defaults
Connection.DEFAULT_PORT = 27017;
<commit_msg>Changed waitForConnection to use setTimeout instead of nextTick, should lower cpu contention<commit_after>var net = require('net'),
  debug = require('util').debug,
  inspect = require('util').inspect,
  EventEmitter = require("events").EventEmitter,
  BinaryParser = require('./bson/binary_parser').BinaryParser,
  inherits = require('util').inherits,
  Server = require('./connections/server').Server,
  binaryutils = require("./bson/binary_utils");

var Connection = exports.Connection = function(host, port, autoReconnect, options) {
  this.options = options == null ? {} : options;
  this.host = host;
  this.port = port;
  this.autoReconnect = autoReconnect;
  this.drained = true;
  // Fetch the poolsize
  this.poolSize = this.options["poolSize"] == null ? 1 : this.options["poolSize"];
  // Reconnect buffer for messages
  this.messages = [];

  // Status messages
  this.sizeOfMessage = 0;
  this.bytesRead = 0;
  this.buffer = '';
  this.stubBuffer = '';
  this.connected = false;
  this.closedConnectionCount = 0;

  // Connection pool variables
  this.pool = [];
  this.poolByReference = {};
  this.poolIndex = 0;
};

inherits(Connection, EventEmitter);

var getConnection = function(self) {
  return self.pool[self.poolIndex++ % self.pool.length];
}

// Setup the connection pool
var setupConnectionPool = function(self, poolSize, reconnect) {
  // Pool off connections and status variables
  var connectionPool = [];
  var connectedTo = 0;
  var errors = 0;
  var connectionError = null;

  // Receive listener
  var receiveListener = function(result, fd) {
    fd = fd == null ? this.fd : fd;

    // Fetch the pool reference
    var conObj = self.poolByReference[fd];

    // Check if we have an unfinished message
    if(conObj != null && conObj.bytesRead > 0 && conObj.sizeOfMessage > 0) {
      // Calculate remaing bytes to fetch
      var remainingBytes = conObj.sizeOfMessage - conObj.bytesRead;
      // Check if we have multiple packet messages and save the pieces otherwise emit the message
      if(remainingBytes > result.length) {
        // Let's copy all the results into a new buffer
        var buffer = new Buffer(conObj.buffer.length + result.length);
        conObj.buffer.copy(buffer, 0, 0, conObj.buffer.length);
        result.copy(buffer, conObj.buffer.length, 0, result.length);
        // Save the reference to the new buffer
        conObj.buffer = buffer;
        // Adjust the number of read bytes
        conObj.bytesRead = conObj.bytesRead + result.length;
      } else {
        // Cut off the remaining message
        var buffer = new Buffer(conObj.buffer.length + remainingBytes);
        conObj.buffer.copy(buffer, 0, 0, conObj.buffer.length);
        result.copy(buffer, conObj.buffer.length, 0, remainingBytes);
        // Emit the message
        self.emit("data", buffer);
        // Reset the variables
        conObj.buffer = new Buffer(0); conObj.bytesRead = 0; conObj.sizeOfMessage = 0;
        // If message is longer than the current one, keep parsing
        if(remainingBytes < result.length) {
          receiveListener(result.slice(remainingBytes, result.length), fd);
        }
      }
    } else if(conObj != null){
      if(conObj.stubBuffer.length > 0) {
        // Add missing stub files
        var buffer = new Buffer(conObj.stubBuffer.length + result.length);
        conObj.stubBuffer.copy(buffer, 0, 0, conObj.stubBuffer.length);
        result.copy(buffer, conObj.stubBuffer.length, 0, result.length);
        // New result contains the former missing bytes as well as the incoming payload
        result = buffer;
        conObj.stubBuffer = new Buffer(0);
      }

      if(result.length > 4) {
        // var sizeOfMessage = BinaryParser.toInt(result.toString('binary', 0, 4));
        var sizeOfMessage = binaryutils.decodeUInt32(result, 0);
        // We got a partial message, store the result and wait for more
        if(sizeOfMessage > result.length) {
          // Create a new buffer with the correct size
          var buffer = new Buffer(conObj.buffer.length + result.length);
          conObj.buffer.copy(buffer, 0, 0, self.buffer.length);
          result.copy(buffer, conObj.buffer.length, 0, result.length);
          conObj.buffer = buffer;
          // Adjust variables
          conObj.bytesRead = result.length; conObj.sizeOfMessage = sizeOfMessage;
        } else if(sizeOfMessage == result.length) {
          self.emit("data", result);
        } else if(sizeOfMessage < result.length) {
          self.emit("data", result.slice(0, sizeOfMessage));
          receiveListener(result.slice(sizeOfMessage, result.length), fd);
        }
      } else {
        conObj.stubBuffer = result;
      }
    }
  };

  // Fill the pool
  for(var i = 0; i < poolSize; i++) {
    // Create the associated connection
    var connection = net.createConnection(self.port, self.host);
    // Add connnect listener
    connection.addListener("connect", function() {
      // this.setEncoding("binary");
      this.setTimeout(0);
      this.setNoDelay();
      // Update number of connected to server
      connectedTo = connectedTo + 1;
    });

    connection.addListener("error", function(err) {
      // Update number of errors
      errors = errors + 1;
      connectionError = err;
    });

    connection.addListener("timeout", function(err) {
      // Update number of errors
      errors = errors + 1;
      connectionError = err;
    });

    // Add a close listener
    connection.addListener("close", function() {
      // Only emit close once all connections in the pool have closed.
      // Note that connections which emit an error also emit a close.
      if (++self.closedConnectionCount === self.pool.length)
        self.emit("close");
    });

    // Add connection to the pool array
    connectionPool.push({"connection": connection,
      "sizeOfMessage": 0,
      "bytesRead": 0,
      "buffer": new Buffer(0),
      "stubBuffer": ''});

    // Add the listener to the connection
    connection.addListener("data", receiveListener);
  }

  // Function that wait for connection to finish up
  var waitForConnections = function() {
    // Emit a connect message once all connections are up
    if(connectedTo == connectionPool.length) {
      if(reconnect == null || !reconnect) {
        self.connected = true;
        self.poolByReference = {};

         // Save the connections by the fd reference
        self.pool.forEach(function(con) {
          self.poolByReference[con.connection.fd] = con;
        });

        self.emit("connect");
      } else {
        self.connected = false;
        self.emit("reconnect");
      }
    } else if(errors > 0) {
      if(reconnect == null || !reconnect) {
        self.connected = false;
        self.emit("error", connectionError);
      } else {
        self.connected = false;
        self.emit("reconnect");
      }
    } else {
      // process.nextTick(waitForConnections);
      setTimeout(waitForConnections, 100);
    }
  }

  // Wait until we are done connected to all pool entries before emitting connect signal
  // process.nextTick(waitForConnections);
  setTimeout(waitForConnections, 100);

  // Return the pool
  return connectionPool;
}

// Functions to open the connection
Connection.prototype.open = function() {
  // Create the pool with connections
  this.pool = setupConnectionPool(this, this.poolSize);
}

Connection.prototype.getConnection = function() {
  return getConnection(this);
}

Connection.prototype.close = function(callback) {
  this.connected = false;
  // Close all entries in the pool
  if (!callback) {
    // This seems to block until completion.
    for(var i = 0; i < this.pool.length; i++) {
      this.pool[i].connection.end();
    }
  } else {
    this.once("close", callback);
    this.pool.forEach(function(poolMember, i) {
      var connection = poolMember.connection;
      // Force an async flush.
      connection.write('', function() {
        // Hopefully this doesn't block.
        connection.end();
      });
    });
  }
};

Connection.prototype.send = function(command, rawConnection) {
  var self = this;
  // If we are executing the commnand on the entire pool
  var connection = null;
  // If we are forcing the use of a connection
  if(rawConnection != null) {
    connection = rawConnection;
  } else {
    connection = getConnection(self).connection;
  }

  // Check if the connection is closed
  try {
    if (connection.readyState != "open") {
      throw 'notConnected';
    }

    // Send the command, if it's an array of commands execute them all on the same connection
    if(Array.isArray(command)) {
      for(var i = 0; i < command.length; i++) {
        connection.write(command[i].toBinary());
      }
    } else {
      connection.write(command.toBinary());
    }
  } catch(err) {
    // Check if the connection is closed
    if(connection.readyState != "open" && self.autoReconnect) {
      // Add the message to the queue of messages to send
      self.messages.push(command);
      // Initiate reconnect if no current running
      if(self.currently_reconnecting == null || self.currently_reconnecting == false) {
        self.currently_reconnecting = true;

        // Create the pool with connections
        self.pool = setupConnectionPool(self, self.poolSize, true);
        self.poolByReference = {};
        // Save the connections by the fd reference
        self.pool.forEach(function(con) {
          self.poolByReference[con.connection.fd] = con;
        })

        // Wait for a reconnect and send all the messages
        self.on("resend", function() {
          self.currently_reconnecting = false;
          // Fire the message again
          while(self.messages.length > 0) {
            // Fetch a connection and resend messages
            connection = getConnection(self).connection;
            // Fetch the a message
            var command = self.messages.shift();
            // Fire
            if(Array.isArray(command)) {
              for(var i = 0; i < command.length; i++) {
                connection.write(command[i].toBinary());
              }
            } else {
              connection.write(command.toBinary());
            }
          }
        })
      }
    } else {
      // Set connected to false
      self.connected = false;
      // Throw error
      throw err;
    }
  }
};

/**
* Wrtie command without an attempt of reconnect
* @param command
*/
Connection.prototype.sendwithoutReconnect = function(command) {
  var self = this;
  var connection = this.connection;

  // Check if the connection is closed
  if (connection.readyState != "open") {
    throw new Error( 'Connection closed!' );
  }

  // If connection.write() throws there is
  // no need to reconnect since called by latest master
  // and already went through send() function
  connection.write(command.toBinary(), "binary");
};

// Some basic defaults
Connection.DEFAULT_PORT = 27017;
<|endoftext|>


<commit_before>/**
 * Copyright 2014 GetHuman LLC
 * Author: Jeff Whelpley
 * Date: 4/16/14
 *
 * Load given set of states into the UI router $stateProvider
 */
angular.module('pancakesAngular').provider('stateLoader', function () {

    /**
     * Helper function to get pascal case of a route name
     * @param val
     * @returns {string}
     */
    function getPascalCase(val) {
        var parts = val.split('.');
        var newVal = '';

        for (var i = 0; i < parts.length; i++) {
            newVal += parts[i].substring(0, 1).toUpperCase() + parts[i].substring(1);
        }

        return newVal;
    }

    /**
     * This is the route loader for angular where we take the input route config data
     * and load it into the UI Router
     *
     * @param $stateProvider
     * @param appName
     * @param routes
     * @param resolves
     * @param isMobile
     */
    this.loadStates = function loadStates($stateProvider, appName, routes, resolves, isMobile) {
        var stateNames = {};
        angular.forEach(routes, function (route) {
            angular.forEach(route.urls, function (url, idx) {
                var sideview = route.sideview || 'default';
                var initialModel = resolves[route.name] || function () { return {}; };
                var stateName = (route.name + (idx === 0 ? '' : '--' + idx)).replace(/\./g, '-');
                var stateConfig = {
                    url: url,
                    resolve: {
                        initialModel: initialModel,
                        loadedUser: ['activeUser', function (activeUser) {
                            return activeUser.init();
                        }]
                    },
                    onEnter: ['$rootScope', function ($rootScope) {
                        $rootScope.stateData = route.data || {};
                        $rootScope.pageLoadTimestamp = (new Date()).getTime();
                    }],
                    views: {
                        '': {
                            controller:     getPascalCase(route.name + 'Ctrl'),
                            templateUrl:    'templates/' + route.name
                        }
                    }
                };
                if ( route.data ) {
                    stateConfig.data = route.data;
                }

                if (!isMobile) {
                    stateConfig.views.sideview = {
                        controller:     getPascalCase(appName + '.sideview.' + sideview + '.ctrl'),
                        templateUrl:    'templates/' + appName + '.sideview.' + sideview
                    };
                }

                // need to make sure state names are unique so just add a timestamp if nothing else
                if (stateNames[stateName]) { stateName += (new Date()).getTime(); }
                stateNames[stateName] = true;

                // add state to the UI Router
                $stateProvider.state(stateName, stateConfig);
            });
        });
    };

    /**
     * This doesn't do anything because we are hijacking the provider angular type
     * so we can get this functionality into the config
     * @returns {{}}
     */
    this.$get = function () { return { getPascalCase: getPascalCase }; };
});
<commit_msg>add shortcut for include ad data in ui-router config from app config/routes<commit_after>/**
 * Copyright 2014 GetHuman LLC
 * Author: Jeff Whelpley
 * Date: 4/16/14
 *
 * Load given set of states into the UI router $stateProvider
 */
angular.module('pancakesAngular').provider('stateLoader', function () {

    /**
     * Helper function to get pascal case of a route name
     * @param val
     * @returns {string}
     */
    function getPascalCase(val) {
        var parts = val.split('.');
        var newVal = '';

        for (var i = 0; i < parts.length; i++) {
            newVal += parts[i].substring(0, 1).toUpperCase() + parts[i].substring(1);
        }

        return newVal;
    }

    /**
     * This is the route loader for angular where we take the input route config data
     * and load it into the UI Router
     *
     * @param $stateProvider
     * @param appName
     * @param routes
     * @param resolves
     * @param isMobile
     */
    this.loadStates = function loadStates($stateProvider, appName, routes, resolves, isMobile) {
        var stateNames = {};
        angular.forEach(routes, function (route) {
            angular.forEach(route.urls, function (url, idx) {
                var sideview = route.sideview || 'default';
                var initialModel = resolves[route.name] || function () { return {}; };
                var stateName = (route.name + (idx === 0 ? '' : '--' + idx)).replace(/\./g, '-');
                var stateConfig = {
                    url: url,
                    resolve: {
                        initialModel: initialModel,
                        loadedUser: ['activeUser', function (activeUser) {
                            return activeUser.init();
                        }]
                    },
                    onEnter: ['$rootScope', function ($rootScope) {
                        $rootScope.stateData = route.data || {};
                        $rootScope.pageLoadTimestamp = (new Date()).getTime();
                    }],
                    views: {
                        '': {
                            controller:     getPascalCase(route.name + 'Ctrl'),
                            templateUrl:    'templates/' + route.name
                        }
                    }
                };
                if ( route.data ) {
                    stateConfig.data = route.data;
                }
                if ( route.ads ) {
                    stateConfig.data = stateConfig.data || {};
                    stateConfig.data.ads = route.ads;
                }

                if (!isMobile) {
                    stateConfig.views.sideview = {
                        controller:     getPascalCase(appName + '.sideview.' + sideview + '.ctrl'),
                        templateUrl:    'templates/' + appName + '.sideview.' + sideview
                    };
                }

                // need to make sure state names are unique so just add a timestamp if nothing else
                if (stateNames[stateName]) { stateName += (new Date()).getTime(); }
                stateNames[stateName] = true;

                // add state to the UI Router
                $stateProvider.state(stateName, stateConfig);
            });
        });
    };

    /**
     * This doesn't do anything because we are hijacking the provider angular type
     * so we can get this functionality into the config
     * @returns {{}}
     */
    this.$get = function () { return { getPascalCase: getPascalCase }; };
});
<|endoftext|>


<commit_before>/** MIT License (c) copyright B Cavalier & J Hann */

(function (define, global) {
define(function (require) {
"use strict";

	var domEvents, fireSimpleEvent, watchNode, undef;

	domEvents = require('./dom/events');
	fireSimpleEvent = domEvents.fireSimpleEvent;
	watchNode = domEvents.watchNode;

	/**
	 * Creates a cola adapter for interacting with dom nodes.  Be sure to
	 * unwatch any watches to prevent memory leaks in Internet Explorer 6-8.
	 * @constructor
	 * @param rootNode {DOMNode}
	 */
	function NodeAdapter (rootNode) {
		var self = this;

		this._rootNode = rootNode;

		// set blank bindings
		this.setBindings({});

		// keep data values
		this._values = {};

	}

	NodeAdapter.prototype = {

		/**
		 * Sets the binding info for this dom tree.
		 * @param bindings
		 */
		setBindings: function (bindings) {
			this._bindings = bindings;
		},

		/**
		 * Watches a specific property and calls back when it changes.
		 * @param name {String} the name of the property to watch.
		 * @param callback {Function} function (propValue, propName) {}
		 * @returns {Function} a function to call when done watching.
		 */
		watch: function (name, callback) {
			var b, currValues;
			b = this._getBindings(name);
			currValues = this._values;
			return listenToNode(b.node, b.events, function() {
				var prev, curr;
				// ensure value has changed
				prev = currValues[name];
				curr = getNodePropOrAttr(b.node, b.prop);
				if (curr != prev) {
					currValues[name] = curr;
					callback(name, curr);
				}
			});
		},

		/**
		 * Watches all nodes that have explicit bindings.
		 * Due to lack of bubbling support for many events, we can't
		 * just listen at the root node. Instead, we have to just
		 * listen to all the nodes that are explicitly bound.
		 * @param callback {Function} function (propValue, propName) {}
		 * @returns {Function} a function to call when done watching.
		 */
		watchAll: function (callback) {
			var unwatchers;
			unwatchers = [];
			for (var p in this._bindings) {
				unwatchers.push(this.watch(p, callback));
			}
			return function () {
				var unwatcher;
				while ((unwatcher = unwatchers.pop())) unwatcher();
			}
		},

		/**
		 * Signals that a property in a synchronized object has changed.
		 * @param name {String} the name of the changed property
		 * @param value the value of the changed property
		 */
		set: function (name, value) {
			var b, current;
			b = this._getBindings(name);
			current = getNodePropOrAttr(b.node, name);
			this._values[name] = current;
			if (current != value) {
				setNodePropOrAttr(b.node, b.prop, value);
				// notify watchers
				fireSimpleEvent(b.node, colaSyntheticEvent);
			}
		},

		/**
		 * Returns the binding info for a node, if it exists.
		 * @param name {String} the name of the node
		 * @returns {Object} {
		 *     node: aNode,
		 *     prop: 'aProp',
		 *     events: 'event1,event2' // optional
		 * }
		 */
		_getBindings: function (name) {
			var bindings, binding;
			bindings = this._bindings;
			if (name in bindings) {
				binding = bindings[name];
			}
			else {
				binding = {
					prop: 'innerHTML',
					// TODO: should we pass these or none?
					events: ['change', 'blur']
				};
			}
			if (!('node' in binding)) {
				binding.node = guessNode(this._rootNode, name);
			}
			return binding;
		}

	};

	/**
	 * Tests whether the given object is a candidate to be handled by
	 * this adapter. Returns true if this is a DOMNode (or looks like one).
	 * @param obj
	 * @returns {Boolean}
	 */
	NodeAdapter.canHandle = function (obj) {
		// crude test if an object is a node.
		return obj && obj.tagName && obj.getAttribute && obj.setAttribute;
	};

	var colaSyntheticEvent, attrToProp;

	colaSyntheticEvent = '-cola-item-prop-updated';

	attrToProp = {
		'class': 'className',
		'for': 'htmlFor'
	};

	/**
	 * Returns a property or attribute of a node.
	 * @param node {DOMNode}
	 * @param name {String}
	 * @returns the value of the property or attribute
	 */
	function getNodePropOrAttr (node, name) {
		if (name in node) {
			return node[attrToProp[name] || name];
		}
		else {
			// TODO: do we need to cast to lower case?
			return node.getAttribute(name);
		}
	}

	/**
	 * Sets a property of a node.
	 * @param node {DOMNode}
	 * @param name {String}
	 * @param value
	 * @returns {DOMNode}
	 */
	function setNodePropOrAttr (node, name, value) {
		if (name in node) {
			node[attrToProp[name] || name] = value;
		}
		else {
			// TODO: do we need to cast to lower case?
			node.setAttribute(name, value);
		}
	}

	function listenToNode (node, events, callback) {

		var unwatchers, i;

		if (typeof events == 'string') {
			events = events.split(/\s*,\s*/);
		}
		else if (!events) {
			events = [];
		}

		// add an event for notifying from the set() method
		events.push(colaSyntheticEvent);

		// create unwatchers
		unwatchers = [];
		for (i = 0; i < events.length; i++) {
			unwatchers.push(watchNode(node, events[i], callback));
		}

		// create and return single unwatcher to unwatch all events
		return function () {
			var unwatch;
			while ((unwatch == unwatchers.pop())) squelchedUnwatch(unwatch);
		};

	}

	function squelchedUnwatch (unwatch) {
		try { unwatch(); } catch (ex) {}
	}

	/**
	 * Crude way to find a node under the current node. This is just a
	 * default implementation. A better one should be injected by
	 * the environment.
	 * @private
	 * @param rootNode
	 * @param nodeName
	 */
	function guessNode (rootNode, nodeName) {
		// use form.elements if this is a form
		if (/^form$/i.test(rootNode.tagName)) {
			return rootNode.elements[nodeName];
		}
		// use getElementById, if not a form (yuk!)
		else {
			return rootNode.ownerDocument.getElementById(nodeName);
		}
	}

	return NodeAdapter;

});
}(
	typeof define == 'function'
		? define
		: function (factory) { module.exports = factory(require); },
	this
));<commit_msg>add syncTo()<commit_after>/** MIT License (c) copyright B Cavalier & J Hann */

(function (define, global) {
define(function (require) {
"use strict";

	var domEvents, fireSimpleEvent, watchNode, undef;

	domEvents = require('./dom/events');
	fireSimpleEvent = domEvents.fireSimpleEvent;
	watchNode = domEvents.watchNode;

	/**
	 * Creates a cola adapter for interacting with dom nodes.  Be sure to
	 * unwatch any watches to prevent memory leaks in Internet Explorer 6-8.
	 * @constructor
	 * @param rootNode {DOMNode}
	 */
	function NodeAdapter (rootNode) {
		var self = this;

		this._rootNode = rootNode;

		// set blank bindings
		this.setBindings({});

		// keep data values
		this._values = {};

	}

	NodeAdapter.prototype = {

		/**
		 * Sets the binding info for this dom tree.
		 * @param bindings
		 */
		setBindings: function (bindings) {
			this._bindings = bindings;
		},

		/**
		 * Watches a specific property and calls back when it changes.
		 * @param name {String} the name of the property to watch.
		 * @param callback {Function} function (propValue, propName) {}
		 * @returns {Function} a function to call when done watching.
		 */
		watch: function (name, callback) {
			var b, currValues;
			b = this._getBindings(name);
			currValues = this._values;
			return listenToNode(b.node, b.events, function() {
				var prev, curr;
				// ensure value has changed
				prev = currValues[name];
				curr = getNodePropOrAttr(b.node, b.prop);
				if (curr != prev) {
					currValues[name] = curr;
					callback(name, curr);
				}
			});
		},

		/**
		 * Watches all nodes that have explicit bindings.
		 * Due to lack of bubbling support for many events, we can't
		 * just listen at the root node. Instead, we have to just
		 * listen to all the nodes that are explicitly bound.
		 * @param callback {Function} function (propValue, propName) {}
		 * @returns {Function} a function to call when done watching.
		 */
		watchAll: function (callback) {
			var unwatchers;
			unwatchers = [];
			for (var p in this._bindings) {
				unwatchers.push(this.watch(p, callback));
			}
			return function () {
				var unwatcher;
				while ((unwatcher = unwatchers.pop())) unwatcher();
			}
		},

		/**
		 * Signals that a property in a synchronized object has changed.
		 * @param name {String} the name of the changed property
		 * @param value the value of the changed property
		 */
		set: function (name, value) {
			var b, current;
			b = this._getBindings(name);
			current = getNodePropOrAttr(b.node, name);
			this._values[name] = current;
			if (current != value) {
				setNodePropOrAttr(b.node, b.prop, value);
				// notify watchers
				fireSimpleEvent(b.node, colaSyntheticEvent);
			}
		},

		syncTo: function (adapter) {
			var p, b;
			for (p in this._bindings) {
				b = this._bindings[p];
				adapter.set(p, getNodePropOrAttr(b.node, b.prop));
			}
		},

		/**
		 * Returns the binding info for a node, if it exists.
		 * @param name {String} the name of the node
		 * @returns {Object} {
		 *     node: aNode,
		 *     prop: 'aProp',
		 *     events: 'event1,event2' // optional
		 * }
		 */
		_getBindings: function (name) {
			var bindings, binding;
			bindings = this._bindings;
			if (name in bindings) {
				binding = bindings[name];
			}
			else {
				binding = {
					prop: 'innerHTML',
					// TODO: should we pass these or none?
					events: ['change', 'blur']
				};
			}
			if (!('node' in binding)) {
				binding.node = guessNode(this._rootNode, name);
			}
			return binding;
		}

	};

	/**
	 * Tests whether the given object is a candidate to be handled by
	 * this adapter. Returns true if this is a DOMNode (or looks like one).
	 * @param obj
	 * @returns {Boolean}
	 */
	NodeAdapter.canHandle = function (obj) {
		// crude test if an object is a node.
		return obj && obj.tagName && obj.getAttribute && obj.setAttribute;
	};

	var colaSyntheticEvent, attrToProp;

	colaSyntheticEvent = '-cola-item-prop-updated';

	attrToProp = {
		'class': 'className',
		'for': 'htmlFor'
	};

	/**
	 * Returns a property or attribute of a node.
	 * @param node {DOMNode}
	 * @param name {String}
	 * @returns the value of the property or attribute
	 */
	function getNodePropOrAttr (node, name) {
		if (name in node) {
			return node[attrToProp[name] || name];
		}
		else {
			// TODO: do we need to cast to lower case?
			return node.getAttribute(name);
		}
	}

	/**
	 * Sets a property of a node.
	 * @param node {DOMNode}
	 * @param name {String}
	 * @param value
	 * @returns {DOMNode}
	 */
	function setNodePropOrAttr (node, name, value) {
		if (name in node) {
			node[attrToProp[name] || name] = value;
		}
		else {
			// TODO: do we need to cast to lower case?
			node.setAttribute(name, value);
		}
	}

	function listenToNode (node, events, callback) {

		var unwatchers, i;

		if (typeof events == 'string') {
			events = events.split(/\s*,\s*/);
		}
		else if (!events) {
			events = [];
		}

		// add an event for notifying from the set() method
		events.push(colaSyntheticEvent);

		// create unwatchers
		unwatchers = [];
		for (i = 0; i < events.length; i++) {
			unwatchers.push(watchNode(node, events[i], callback));
		}

		// create and return single unwatcher to unwatch all events
		return function () {
			var unwatch;
			while ((unwatch == unwatchers.pop())) squelchedUnwatch(unwatch);
		};

	}

	function squelchedUnwatch (unwatch) {
		try { unwatch(); } catch (ex) {}
	}

	/**
	 * Crude way to find a node under the current node. This is just a
	 * default implementation. A better one should be injected by
	 * the environment.
	 * @private
	 * @param rootNode
	 * @param nodeName
	 */
	function guessNode (rootNode, nodeName) {
		// use form.elements if this is a form
		if (/^form$/i.test(rootNode.tagName)) {
			return rootNode.elements[nodeName];
		}
		// use getElementById, if not a form (yuk!)
		else {
			return rootNode.ownerDocument.getElementById(nodeName);
		}
	}

	return NodeAdapter;

});
}(
	typeof define == 'function'
		? define
		: function (factory) { module.exports = factory(require); },
	this
));<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var uptown = require('uptown');
var createClass = uptown.createClass;
var KeyValuePair = require('../key-value-pair');
var ArraySlice = require('../array-slice.js');

/**
 * @class
 *
 * @param content
 * @param meta
 * @param attributes
 *
 * @property {string} element
 */
var Element = createClass({
  constructor: function(content, meta, attributes) {
    // Lazy load this.meta and this.attributes because it's a Minim element
    // Otherwise, we get into circuluar calls
    if (meta) {
      this.meta = meta;
    }

    if (attributes) {
      this.attributes = attributes;
    }

    this.content = content !== undefined ? content : null;
  },

  /**
   * Freezes the element to prevent any mutation.
   * A frozen element will add `parent` property to every child element
   * to allow traversing up the element tree.
   *
   * @memberof Element.prototype
   */
  freeze: function() {
    if (this._meta) {
      this.meta.parent = this;
      this.meta.freeze();
    }

    if (this._attributes) {
      this.attributes.parent = this;
      this.attributes.freeze();
    }

    this.children.forEach(function (element) {
      element.parent = this;
      element.freeze();
    }, this);

    if (this.content && Array.isArray(this.content)) {
      Object.freeze(this.content);
    }

    Object.freeze(this);
  },

  primitive: function() {
    return;
  },

  /**
   * Creates a deep clone of the instance
   * @memberof Element.prototype
   */
  clone: function() {
    var copy = new this.constructor();

    copy.element = this.element;

    if (this.meta.length) {
      copy._meta = this.meta.clone();
    }

    if (this.attributes.length) {
      copy._attributes = this.attributes.clone();
    }

    if (this.content) {
      if (this.content.clone) {
        copy.content = this.content.clone();
      } else if (Array.isArray(this.content)) {
        copy.content = this.content.map(function (element) {
          return element.clone();
        });
      } else {
        copy.content = this.content;
      }
    } else {
      copy.content = this.content;
    }

    return copy;
  },

  /**
   * @memberof Element.prototype
   */
  toValue: function() {
    if (this.content instanceof Element) {
      return this.content.toValue();
    }

    if (this.content instanceof KeyValuePair) {
      return {
        key: this.content.key.toValue(),
        value: this.content.value.toValue()
      };
    }

    if (this.content && this.content.map) {
      return this.content.map(function(element) {
        return element.toValue();
      }, this);
    }

    return this.content;
  },

  /**
   * Creates a reference pointing at the Element
   * @returns {RefElement}
   * @memberof Element.prototype
   */
  toRef: function(path) {
    if (this.id.toValue() === '') {
      throw Error('Cannot create reference to an element that does not contain an ID');
    }

    var RefElement = require('../elements/ref-element');
    var ref = new RefElement(this.id.toValue());

    if (path) {
      ref.path = path;
    }

    return ref;
  },

  /**
   * Finds the given elements in the element tree.
   * When providing multiple element names, you must first freeze the element.
   *
   * @param {...elementNames}
   * @returns {ArraySlice}
   * @memberof Element.prototype
   */
  findRecursive: function() {
    if (arguments.length > 1 && !this.isFrozen) {
      throw new Error('Cannot find recursive with multiple element names without first freezing the element. Call `element.freeze()`');
    }

    var elementNames = [].concat.apply([], arguments);
    var elementName = elementNames.pop();
    var elements = new ArraySlice();

    var append = function(array, element) {
      array.push(element);
      return array;
    };

    // Checks the given element and appends element/sub-elements
    // that match element name to given array
    var checkElement = function(array, element) {
      if (element.element === elementName) {
        array.push(element);
      }

      var items = element.findRecursive(elementName);
      if (items) {
        items.reduce(append, array);
      }

      if (element.content instanceof KeyValuePair) {
        if (element.content.key) {
          checkElement(array, element.content.key);
        }

        if (element.content.value) {
          checkElement(array, element.content.value);
        }
      }

      return array;
    };

    if (this.content) {
      // Direct Element
      if (this.content.element) {
        checkElement(elements, this.content);
      }

      // Element Array
      if (Array.isArray(this.content)) {
        this.content.reduce(checkElement, elements);
      }
    }

    if (!elementNames.isEmpty) {
      elements = elements.filter(function (element) {
        var parentElements = element.parents.map(function (element) {
          return element.element;
        });

        for (var namesIndex in elementNames) {
          var name = elementNames[namesIndex];
          var index = parentElements.indexOf(name);

          if (index !== -1) {
            parentElements = parentElements.splice(0, index);
          } else {
            return false;
          }
        }

        return true;
      });
    }

    return elements;
  },

  set: function(content) {
    this.content = content;
    return this;
  },

  equals: function(value) {
    return _.isEqual(this.toValue(), value);
  },

  getMetaProperty: function(name, value) {
    if (!this.meta.hasKey(name)) {
      if (this.isFrozen) {
        var refract = require('../refraction').refract;
        var element = refract(value);
        element.freeze();
        return element;
      }

      this.meta.set(name, value);
    }

    return this.meta.get(name);
  },

  setMetaProperty: function(name, value) {
    this.meta.set(name, value);
  }
}, {}, {
  /**
   * @type String
   * @memberof Element.prototype
   */
  element: {
    get: function() {
      // Returns 'element' so we don't have undefined as element
      return this._storedElement || 'element';
    },
    set: function(element) {
      this._storedElement = element;
    }
  },

  /**
   * @type ObjectElement
   * @memberof Element.prototype
   */
  meta: {
    get: function() {
      if (!this._meta) {
        var ObjectElement = require('./object-element');

        if (this.isFrozen) {
          var meta = new ObjectElement();
          meta.freeze();
          return meta;
        }

        this._meta = new ObjectElement();
      }

      return this._meta;
    },
    set: function(value) {
      var ObjectElement = require('./object-element');

      if (value instanceof ObjectElement) {
        this._meta = value;
      } else {
        this.meta.set(value || {});
      }
    }
  },

  /**
   * @type ObjectElement
   * @memberof Element.prototype
   */
  attributes: {
    get: function() {
      if (!this._attributes) {
        var ObjectElement = require('./object-element');

        if (this.isFrozen) {
          var meta = new ObjectElement();
          meta.freeze();
          return meta;
        }

        this._attributes = new ObjectElement();
      }

      return this._attributes;
    },
    set: function(value) {
      var ObjectElement = require('./object-element');
      if (value instanceof ObjectElement) {
        this._attributes = value;
      } else {
        this.attributes.set(value || {});
      }
    }
  },

  /**
   * @type StringElement
   * @memberof Element.prototype
   */
  id: {
    get: function() {
      return this.getMetaProperty('id', '');
    },
    set: function(element) {
      this.setMetaProperty('id', element);
    }
  },

  /**
   * @type ArrayElement
   * @memberof Element.prototype
   */
  classes: {
    get: function() {
      return this.getMetaProperty('classes', []);
    },
    set: function(element) {
      this.setMetaProperty('classes', element);
    }
  },

  /**
   * @type StringElement
   * @memberof Element.prototype
   */
  title: {
    get: function() {
      return this.getMetaProperty('title', '');
    },
    set: function(element) {
      this.setMetaProperty('title', element);
    }
  },

  /**
   * @type StringElement
   * @memberof Element.prototype
   */
  description: {
    get: function() {
      return this.getMetaProperty('description', '');
    },
    set: function(element) {
      this.setMetaProperty('description', element);
    }
  },

  /**
   * @type ArrayElement
   * @memberof Element.prototype
   */
  links: {
    get: function() {
      return this.getMetaProperty('links', []);
    },
    set: function(element) {
      this.setMetaProperty('links', element);
    }
  },

  /**
   * @type boolean
   * @readonly
   * @memberof Element.prototype
   */
  isFrozen: {
    get: function() {
      return Object.isFrozen(this);
    }
  },

  /**
   * @type ArraySlice
   * @readonly
   * @memberof Element.prototype
   */
  parents: {
    get: function() {
      var parent = this.parent;
      var parents = new ArraySlice();

      while (parent) {
        parents.push(parent);
        parent = parent.parent;
      }

      return parents;
    }
  },

  /**
   * @type ArraySlice
   * @readonly
   * @memberof Element.prototype
   */
  children: {
    get: function() {
      var ArrayElement = require('./array-element');

      if (Array.isArray(this.content)) {
        return new ArraySlice(this.content);
      } else if (this.content instanceof KeyValuePair) {
        var children = new ArraySlice([this.content.key]);

        if (this.content.value) {
          children.push(this.content.value);
        }

        return children;
      } else if (this.content instanceof Element) {
        return new ArraySlice([this.content]);
      } else {
        return new ArraySlice();
      }
    }
  },

  /**
   * @type ArraySlice
   * @readonly
   * @memberof Element.prototype
   */
  recursiveChildren: {
    get: function() {
      var children = new ArraySlice();

      this.children.forEach(function (element) {
        children.push(element);

        element.recursiveChildren.forEach(function (child) {
          children.push(child);
        });
      });

      return children;
    }
  }
});

module.exports = Element;
<commit_msg>refactor(element): Cache ObjectElement import<commit_after>'use strict';

var _ = require('lodash');
var uptown = require('uptown');
var createClass = uptown.createClass;
var KeyValuePair = require('../key-value-pair');
var ArraySlice = require('../array-slice.js');

var ObjectElement;

function loadObjectElement() {
  if (!ObjectElement) {
    ObjectElement = require('./object-element');
  }
}

/**
 * @class
 *
 * @param content
 * @param meta
 * @param attributes
 *
 * @property {string} element
 */
var Element = createClass({
  constructor: function(content, meta, attributes) {
    // Lazy load this.meta and this.attributes because it's a Minim element
    // Otherwise, we get into circuluar calls
    if (meta) {
      this.meta = meta;
    }

    if (attributes) {
      this.attributes = attributes;
    }

    this.content = content !== undefined ? content : null;
  },

  /**
   * Freezes the element to prevent any mutation.
   * A frozen element will add `parent` property to every child element
   * to allow traversing up the element tree.
   *
   * @memberof Element.prototype
   */
  freeze: function() {
    if (this._meta) {
      this.meta.parent = this;
      this.meta.freeze();
    }

    if (this._attributes) {
      this.attributes.parent = this;
      this.attributes.freeze();
    }

    this.children.forEach(function (element) {
      element.parent = this;
      element.freeze();
    }, this);

    if (this.content && Array.isArray(this.content)) {
      Object.freeze(this.content);
    }

    Object.freeze(this);
  },

  primitive: function() {
    return;
  },

  /**
   * Creates a deep clone of the instance
   * @memberof Element.prototype
   */
  clone: function() {
    var copy = new this.constructor();

    copy.element = this.element;

    if (this.meta.length) {
      copy._meta = this.meta.clone();
    }

    if (this.attributes.length) {
      copy._attributes = this.attributes.clone();
    }

    if (this.content) {
      if (this.content.clone) {
        copy.content = this.content.clone();
      } else if (Array.isArray(this.content)) {
        copy.content = this.content.map(function (element) {
          return element.clone();
        });
      } else {
        copy.content = this.content;
      }
    } else {
      copy.content = this.content;
    }

    return copy;
  },

  /**
   * @memberof Element.prototype
   */
  toValue: function() {
    if (this.content instanceof Element) {
      return this.content.toValue();
    }

    if (this.content instanceof KeyValuePair) {
      return {
        key: this.content.key.toValue(),
        value: this.content.value.toValue()
      };
    }

    if (this.content && this.content.map) {
      return this.content.map(function(element) {
        return element.toValue();
      }, this);
    }

    return this.content;
  },

  /**
   * Creates a reference pointing at the Element
   * @returns {RefElement}
   * @memberof Element.prototype
   */
  toRef: function(path) {
    if (this.id.toValue() === '') {
      throw Error('Cannot create reference to an element that does not contain an ID');
    }

    var RefElement = require('../elements/ref-element');
    var ref = new RefElement(this.id.toValue());

    if (path) {
      ref.path = path;
    }

    return ref;
  },

  /**
   * Finds the given elements in the element tree.
   * When providing multiple element names, you must first freeze the element.
   *
   * @param {...elementNames}
   * @returns {ArraySlice}
   * @memberof Element.prototype
   */
  findRecursive: function() {
    if (arguments.length > 1 && !this.isFrozen) {
      throw new Error('Cannot find recursive with multiple element names without first freezing the element. Call `element.freeze()`');
    }

    var elementNames = [].concat.apply([], arguments);
    var elementName = elementNames.pop();
    var elements = new ArraySlice();

    var append = function(array, element) {
      array.push(element);
      return array;
    };

    // Checks the given element and appends element/sub-elements
    // that match element name to given array
    var checkElement = function(array, element) {
      if (element.element === elementName) {
        array.push(element);
      }

      var items = element.findRecursive(elementName);
      if (items) {
        items.reduce(append, array);
      }

      if (element.content instanceof KeyValuePair) {
        if (element.content.key) {
          checkElement(array, element.content.key);
        }

        if (element.content.value) {
          checkElement(array, element.content.value);
        }
      }

      return array;
    };

    if (this.content) {
      // Direct Element
      if (this.content.element) {
        checkElement(elements, this.content);
      }

      // Element Array
      if (Array.isArray(this.content)) {
        this.content.reduce(checkElement, elements);
      }
    }

    if (!elementNames.isEmpty) {
      elements = elements.filter(function (element) {
        var parentElements = element.parents.map(function (element) {
          return element.element;
        });

        for (var namesIndex in elementNames) {
          var name = elementNames[namesIndex];
          var index = parentElements.indexOf(name);

          if (index !== -1) {
            parentElements = parentElements.splice(0, index);
          } else {
            return false;
          }
        }

        return true;
      });
    }

    return elements;
  },

  set: function(content) {
    this.content = content;
    return this;
  },

  equals: function(value) {
    return _.isEqual(this.toValue(), value);
  },

  getMetaProperty: function(name, value) {
    if (!this.meta.hasKey(name)) {
      if (this.isFrozen) {
        var refract = require('../refraction').refract;
        var element = refract(value);
        element.freeze();
        return element;
      }

      this.meta.set(name, value);
    }

    return this.meta.get(name);
  },

  setMetaProperty: function(name, value) {
    this.meta.set(name, value);
  }
}, {}, {
  /**
   * @type String
   * @memberof Element.prototype
   */
  element: {
    get: function() {
      // Returns 'element' so we don't have undefined as element
      return this._storedElement || 'element';
    },
    set: function(element) {
      this._storedElement = element;
    }
  },

  /**
   * @type ObjectElement
   * @memberof Element.prototype
   */
  meta: {
    get: function() {
      if (!this._meta) {
        loadObjectElement();

        if (this.isFrozen) {
          var meta = new ObjectElement();
          meta.freeze();
          return meta;
        }

        this._meta = new ObjectElement();
      }

      return this._meta;
    },
    set: function(value) {
      loadObjectElement();

      if (value instanceof ObjectElement) {
        this._meta = value;
      } else {
        this.meta.set(value || {});
      }
    }
  },

  /**
   * @type ObjectElement
   * @memberof Element.prototype
   */
  attributes: {
    get: function() {
      if (!this._attributes) {
        loadObjectElement();

        if (this.isFrozen) {
          var meta = new ObjectElement();
          meta.freeze();
          return meta;
        }

        this._attributes = new ObjectElement();
      }

      return this._attributes;
    },
    set: function(value) {
      loadObjectElement();

      if (value instanceof ObjectElement) {
        this._attributes = value;
      } else {
        this.attributes.set(value || {});
      }
    }
  },

  /**
   * @type StringElement
   * @memberof Element.prototype
   */
  id: {
    get: function() {
      return this.getMetaProperty('id', '');
    },
    set: function(element) {
      this.setMetaProperty('id', element);
    }
  },

  /**
   * @type ArrayElement
   * @memberof Element.prototype
   */
  classes: {
    get: function() {
      return this.getMetaProperty('classes', []);
    },
    set: function(element) {
      this.setMetaProperty('classes', element);
    }
  },

  /**
   * @type StringElement
   * @memberof Element.prototype
   */
  title: {
    get: function() {
      return this.getMetaProperty('title', '');
    },
    set: function(element) {
      this.setMetaProperty('title', element);
    }
  },

  /**
   * @type StringElement
   * @memberof Element.prototype
   */
  description: {
    get: function() {
      return this.getMetaProperty('description', '');
    },
    set: function(element) {
      this.setMetaProperty('description', element);
    }
  },

  /**
   * @type ArrayElement
   * @memberof Element.prototype
   */
  links: {
    get: function() {
      return this.getMetaProperty('links', []);
    },
    set: function(element) {
      this.setMetaProperty('links', element);
    }
  },

  /**
   * @type boolean
   * @readonly
   * @memberof Element.prototype
   */
  isFrozen: {
    get: function() {
      return Object.isFrozen(this);
    }
  },

  /**
   * @type ArraySlice
   * @readonly
   * @memberof Element.prototype
   */
  parents: {
    get: function() {
      var parent = this.parent;
      var parents = new ArraySlice();

      while (parent) {
        parents.push(parent);
        parent = parent.parent;
      }

      return parents;
    }
  },

  /**
   * @type ArraySlice
   * @readonly
   * @memberof Element.prototype
   */
  children: {
    get: function() {
      var ArrayElement = require('./array-element');

      if (Array.isArray(this.content)) {
        return new ArraySlice(this.content);
      } else if (this.content instanceof KeyValuePair) {
        var children = new ArraySlice([this.content.key]);

        if (this.content.value) {
          children.push(this.content.value);
        }

        return children;
      } else if (this.content instanceof Element) {
        return new ArraySlice([this.content]);
      } else {
        return new ArraySlice();
      }
    }
  },

  /**
   * @type ArraySlice
   * @readonly
   * @memberof Element.prototype
   */
  recursiveChildren: {
    get: function() {
      var children = new ArraySlice();

      this.children.forEach(function (element) {
        children.push(element);

        element.recursiveChildren.forEach(function (child) {
          children.push(child);
        });
      });

      return children;
    }
  }
});

module.exports = Element;
<|endoftext|>


<commit_before>const SvgRelation = require('./SvgRelation');
const AssetGraph = require('../AssetGraph');

class SvgStyle extends SvgRelation {
    get href() {
        return undefined;
    }

    set href(href) {
        const document = this.from.parseTree;
        const styleSheetNode = document.createProcessingInstruction('xml-stylesheet', 'type="' + this.to.contentType + '" href="' + href + '"');

        document.insertBefore(styleSheetNode, document.getElementsByTagName('svg')[0]);

        const xmlStylesheet = this.from.addRelation({
            type: 'XmlStylesheet',
            to: this.to,
            node: styleSheetNode
        });

        // Cleanup
        this.node.parentNode.removeChild(this.node);
        this.from.removeRelation(this);

        this.from.markDirty();

        return xmlStylesheet;
    }

    inline() {}

    attach(position, adjacentRelation) {
        const parseTree = this.from.parseTree;

        this.node = this.node || parseTree.createElement('style');
        this.node.appendChild(parseTree.createTextNode(this.to.text));

        return super.attach(position, adjacentRelation);
    }
};

SvgStyle.prototype.targetType = 'Css';

module.exports = SvgStyle;
<commit_msg>Fix unused require<commit_after>const SvgRelation = require('./SvgRelation');

class SvgStyle extends SvgRelation {
    get href() {
        return undefined;
    }

    set href(href) {
        const document = this.from.parseTree;
        const styleSheetNode = document.createProcessingInstruction('xml-stylesheet', 'type="' + this.to.contentType + '" href="' + href + '"');

        document.insertBefore(styleSheetNode, document.getElementsByTagName('svg')[0]);

        const xmlStylesheet = this.from.addRelation({
            type: 'XmlStylesheet',
            to: this.to,
            node: styleSheetNode
        });

        // Cleanup
        this.node.parentNode.removeChild(this.node);
        this.from.removeRelation(this);

        this.from.markDirty();

        return xmlStylesheet;
    }

    inline() {}

    attach(position, adjacentRelation) {
        const parseTree = this.from.parseTree;

        this.node = this.node || parseTree.createElement('style');
        this.node.appendChild(parseTree.createTextNode(this.to.text));

        return super.attach(position, adjacentRelation);
    }
};

SvgStyle.prototype.targetType = 'Css';

module.exports = SvgStyle;
<|endoftext|>


<commit_before>var fs = require('fs'),
    path = require('path');

function extToLang (ext) {
  var lang;

  return lang;
}

// same as requireService, only with sync interface ( useful for requiring services as packages )
module['exports'] = function requireServiceSync (opts) {

   var service = {};
   // console.log(opts.path)

   if(typeof opts.language === "undefined") {
     var ext = path.extname(opts.path);
     var extentions = {
       ".js": "javascript",
       ".coffee": "coffee-script",
       ".lua": "lua",
       ".php": "php",
       ".pl": "perl",
       ".py": "python", // Remark: You can also use the "--language python" option
       ".py3": "python3", // Remark: You can also use the "--language python3" option
       ".sh": "bash",
       ".rb": "ruby",
       ".tcl": "tcl",
       ".ss": "scheme",
       ".st": "smalltalk"
     };
     opts.language = extentions[ext];
   }

   opts.path = opts.path;

   if (typeof opts.path === "undefined") {
     cb(new Error('`path` is a required argument.'));
   }

   if (typeof opts.language === "undefined") {
     // cb(new Error('`language` is a required argument.'));
   }

   // take stat of path to determine if its file / folder / missing
   var stat;

   try {
     stat = fs.statSync(opts.path);
   } catch (err) {
     throw err;
   }

   if (stat.isDirectory()) {
     return loadService(opts.path, 'dir');
   } else {
     return loadService(opts.path, 'file');
   }

};

function loadService (p, type) {

  var service = {
    name: p,
    owner: "examples"
  };

  var _service = {
    service: "index.js",
    view: "view.html",
    presenter: "presenter.js",
    schema: "schema.js"
  };

  var pkg;

  if (type === "file") {
  } else {
    try {
      pkg = JSON.parse(fs.readFileSync(path.resolve(p) + "/package.json").toString());
      service.pkg = pkg;
    } catch (err) {
      // pkg not available, dont use
      throw err;
    }
  }

  if (typeof pkg === "object") {
    if (pkg.name) {
      service.name = pkg.name;
    }
    if (pkg.author) {
      service.owner = pkg.author;
    }
    if (pkg.main) {
      _service.service = pkg.main;
    }
    if (pkg.view) {
      _service.view = pkg.view;
    }
    if (pkg.presenter) {
      _service.presenter = pkg.presenter;
    }
  }

  var extentions = {
    ".js": "javascript",
    ".coffee": "coffee-script",
    ".lua": "lua",
    ".php": "php",
    ".pl": "perl",
    ".py": "python", // Remark: You can also use the "--language python" option
    ".py3": "python3", // Remark: You can also use the "--language python3" option
    ".sh": "bash",
    ".rb": "ruby",
    ".tcl": "tcl",
    ".ss": "scheme",
    ".st": "smalltalk"
  };
  var ext;

  // source is legacy parameter, now replaced with code
  if (type === "file") {
    ext = path.extname(path.resolve(p));
    service.language = extentions[ext];
    service.source = fs.readFileSync(path.resolve(p)).toString();
    service.code = fs.readFileSync(path.resolve(p)).toString();
  } else {
    ext = path.extname(_service.service);
    service.language = extentions[ext];
    service.source = fs.readFileSync(path.resolve(p) + "/" + _service.service).toString();
    service.code = fs.readFileSync(path.resolve(p) + "/" + _service.service).toString();
  }

  try {
    service.presenter = fs.readFileSync(path.resolve(p) + "/" + _service.presenter).toString();
    //service.presenter = require(path.resolve(p) + "/" + _service.presenter);
  } catch (err) {
    // console.log(err);
  }

  try {
    service.view = fs.readFileSync(path.resolve(p) + "/" + _service.view).toString();
  } catch (err) {
    // console.log(err);
  }

  try {
    // service.schema = fs.readFileSync(path.resolve(p) + "/" + _service.schema).toString();
    service.schema = require(path.resolve(p) + "/" + _service.schema);
  } catch (err) {
    console.log(err);
  }
  console.log(service)
  return service;
 }<commit_msg>[minor] Remove errant console.log<commit_after>var fs = require('fs'),
    path = require('path');

function extToLang (ext) {
  var lang;

  return lang;
}

// same as requireService, only with sync interface ( useful for requiring services as packages )
module['exports'] = function requireServiceSync (opts) {

   var service = {};
   // console.log(opts.path)

   if(typeof opts.language === "undefined") {
     var ext = path.extname(opts.path);
     var extentions = {
       ".js": "javascript",
       ".coffee": "coffee-script",
       ".lua": "lua",
       ".php": "php",
       ".pl": "perl",
       ".py": "python", // Remark: You can also use the "--language python" option
       ".py3": "python3", // Remark: You can also use the "--language python3" option
       ".sh": "bash",
       ".rb": "ruby",
       ".tcl": "tcl",
       ".ss": "scheme",
       ".st": "smalltalk"
     };
     opts.language = extentions[ext];
   }

   opts.path = opts.path;

   if (typeof opts.path === "undefined") {
     cb(new Error('`path` is a required argument.'));
   }

   if (typeof opts.language === "undefined") {
     // cb(new Error('`language` is a required argument.'));
   }

   // take stat of path to determine if its file / folder / missing
   var stat;

   try {
     stat = fs.statSync(opts.path);
   } catch (err) {
     throw err;
   }

   if (stat.isDirectory()) {
     return loadService(opts.path, 'dir');
   } else {
     return loadService(opts.path, 'file');
   }

};

function loadService (p, type) {

  var service = {
    name: p,
    owner: "examples"
  };

  var _service = {
    service: "index.js",
    view: "view.html",
    presenter: "presenter.js",
    schema: "schema.js"
  };

  var pkg;

  if (type === "file") {
  } else {
    try {
      pkg = JSON.parse(fs.readFileSync(path.resolve(p) + "/package.json").toString());
      service.pkg = pkg;
    } catch (err) {
      // pkg not available, dont use
      throw err;
    }
  }

  if (typeof pkg === "object") {
    if (pkg.name) {
      service.name = pkg.name;
    }
    if (pkg.author) {
      service.owner = pkg.author;
    }
    if (pkg.main) {
      _service.service = pkg.main;
    }
    if (pkg.view) {
      _service.view = pkg.view;
    }
    if (pkg.presenter) {
      _service.presenter = pkg.presenter;
    }
  }

  var extentions = {
    ".js": "javascript",
    ".coffee": "coffee-script",
    ".lua": "lua",
    ".php": "php",
    ".pl": "perl",
    ".py": "python", // Remark: You can also use the "--language python" option
    ".py3": "python3", // Remark: You can also use the "--language python3" option
    ".sh": "bash",
    ".rb": "ruby",
    ".tcl": "tcl",
    ".ss": "scheme",
    ".st": "smalltalk"
  };
  var ext;

  // source is legacy parameter, now replaced with code
  if (type === "file") {
    ext = path.extname(path.resolve(p));
    service.language = extentions[ext];
    service.source = fs.readFileSync(path.resolve(p)).toString();
    service.code = fs.readFileSync(path.resolve(p)).toString();
  } else {
    ext = path.extname(_service.service);
    service.language = extentions[ext];
    service.source = fs.readFileSync(path.resolve(p) + "/" + _service.service).toString();
    service.code = fs.readFileSync(path.resolve(p) + "/" + _service.service).toString();
  }

  try {
    service.presenter = fs.readFileSync(path.resolve(p) + "/" + _service.presenter).toString();
    //service.presenter = require(path.resolve(p) + "/" + _service.presenter);
  } catch (err) {
    // console.log(err);
  }

  try {
    service.view = fs.readFileSync(path.resolve(p) + "/" + _service.view).toString();
  } catch (err) {
    // console.log(err);
  }

  try {
    // service.schema = fs.readFileSync(path.resolve(p) + "/" + _service.schema).toString();
    service.schema = require(path.resolve(p) + "/" + _service.schema);
  } catch (err) {
    // console.log(err);
  }

  return service;
 }<|endoftext|>


<commit_before>'use strict';

const requestParser = require('flora-request-parser');
const has = require('has');

const configLoader = require('./config-loader');
const parseXml = require('./xml-reader');
const configParser = require('./config-parser');
const requestResolver = require('./request-resolver');
const dataSourceExecutor = require('./datasource-executor');
const resultBuilder = require('./result-builder');

function explainDataSourceTree(dst, full) {
    const explain = {};

    if (dst.subFilters) {
        explain.subFilters = dst.subFilters.map(
            subFilter => explainDataSourceTree(subFilter, full)
        );
    }

    // TODO: Move requestName generation into request-resolver:
    const requestName = dst.attributePath
        ? dst.attributePath.join('.') + ':' + dst.dataSourceName
        : 'unnamedRequest';

    explain.requestName = requestName;
    explain.type = dst.request.type;

    if (full) {
        explain.attributes = dst.request.attributes ? dst.request.attributes.join(',') : undefined;
        explain.filter = dst.request.filter
            ? dst.request.filter.map(orFilter => orFilter.map((andFilter) => {
                const operators = {
                    like: '~',
                    equal: '=',
                    notEqual: '!=',
                    less: '<',
                    lessOrEqual: '<=',
                    greater: '>',
                    greaterOrEqual: '>=',
                };

                function renderRecursiveArray(array) {
                    if (!Array.isArray(array)) return array;
                    return '[' + array.map(renderRecursiveArray).join(',') + ']';
                }

                return renderRecursiveArray(andFilter.attribute)
                    + (operators[andFilter.operator]
                        ? operators[andFilter.operator]
                        : ' ' + andFilter.operator + ' ')
                    + renderRecursiveArray(andFilter.value)
                    + (andFilter.valueFromParentKey ? '{from-parent-key}' : '')
                    + (has(andFilter, 'valueFromSubFilter') ? `{from-sub-filter: ${andFilter.valueFromSubFilter}}` : '');
            })
                .join(' AND '))
                .join(' OR ')
            : undefined;
        explain.search = dst.request.search;
        explain.order = dst.request.order
            ? dst.request.order.map(order => (order.attribute + ':' + order.direction)).join(',')
            : undefined;
        explain.limit = dst.request.limit;
        explain.page = dst.request.page;
    }

    if (dst.request._explain) {
        Object.assign(explain, dst.request._explain);
    } else {
        explain.executed = false;
    }

    if (dst.subRequests) {
        explain.subRequests = dst.subRequests.map(
            subRequest => explainDataSourceTree(subRequest, full)
        );
    }

    return explain;
}

/**
 * Provides a clean interface for DataSource configurations and their
 * relations.
 */
class ResourceProcessor {
    /**
     * @param {Api} api - The Api instance context.
     */
    constructor(api) {
        this.log = api.log.child({ component: 'resource-processor' });
        this.api = api;
        this.resourceConfigs = {};
    }

    /**
     * Initialize data sources.
     *
     * @param {Object} config - Configuration object
     * @param {Object} config.resourcePath - Path to resource files
     * @return {Promise}
     */
    async init(config = {}) {
        if (!config.resourcesPath) {
            this.log.warn('No resourcesPath in configuration, not loading any resources');
            return null;
        }

        this.log.debug('Loading configuration');

        const configs = await configLoader(this.api, {
            directory: config.resourcesPath,
            parsers: { xml: parseXml }
        });

        this.resourceConfigs = configs;
        this.log.debug('Parsing configs');
        configParser(this.resourceConfigs, this.api.dataSources);

        return this._initResources();
    }

    _initResources() {
        this.log.debug('Initializing resources');

        const resources = this.resourceConfigs;

        return Promise.all(Object.keys(resources).map(resourceName => new Promise((resolve) => {
            this.log.trace('Initializing resource "' + resourceName + '"');

            // no need for "init" extension for the default resource
            if (!resources[resourceName].instance) return resolve();

            // Extension: "init"
            const resource = resources[resourceName].instance;
            if (resource.extensions && resource.extensions.init && typeof resource.extensions.init === 'function') {
                this.log.trace('"init" extension (%s)', resourceName);
                resolve(resource.extensions.init());
            }

            return resolve();
        })));
    }

    /**
     * Handle a request.
     *
     * @param {Request} request - Request object
     * @param {Response} response - Response object (for modifying header information, etc.)
     * @return {Object}
     */
    async handle(request, response) {
        let resolvedDataSourceTree = null;

        // Extension: "request" (resource)
        this.log.trace('handle: "request" extensions (resource)');
        const resource = this.api.getResource(request.resource);
        if (resource && resource.extensions && resource.extensions.request) {
            this.api.log.trace('handle: "request" extension (%s)', request.resource);
            await resource.extensions.request({ request, response });
        }

        // requestParser
        this.log.trace('handle: requestParser');
        let profiler;
        let requestAST;
        try {
            profiler = request._profiler.child('requestParser');
            requestAST = requestParser.parse(request);
            profiler.end();
        } catch (e) {
            profiler.end();
            throw e;
        }

        // requestResolver
        this.log.trace('handle: requestResolver');
        let resolved;
        try {
            profiler = request._profiler.child('requestResolver');
            resolved = requestResolver(requestAST, this.resourceConfigs);
            profiler.end();
            resolvedDataSourceTree = resolved.dataSourceTree;
        } catch (e) {
            profiler.end();
            throw e;
        }

        let results;

        try {
            // dataSourceExecutor
            this.log.trace('handle: dataSourceExecutor');
            resolved.dataSourceTree._profiler = request._profiler.child('dataSourceExecutor');
            let rawResults;
            try {
                rawResults = await dataSourceExecutor(this.api, request, resolved.dataSourceTree);
            } catch (e) {
                resolved.dataSourceTree._profiler.end();
                throw e;
            }

            // resultBuilder
            this.log.trace('handle: resultBuilder');
            try {
                profiler = request._profiler.child('resultBuilder');
                results = resultBuilder(this.api, request, rawResults, resolved.resolvedConfig);
                profiler.end();
            } catch (e) {
                profiler.end();
                throw e;
            }
        } catch (err) {
            // explain even in case of an error
            if ((request._explain === '1' || request._explain === 'full') && this.api.config.allowExplain) {
                err.meta = err.meta || {};
                err.meta.explain = resolvedDataSourceTree
                    ? explainDataSourceTree(resolvedDataSourceTree, request._explain === 'full')
                    : { error: 'Unresolveable request' };
            }

            throw err;
        }

        if ((request._explain === '1' || request._explain === 'full') && this.api.config.allowExplain) {
            response.meta.explain = resolvedDataSourceTree
                ? explainDataSourceTree(resolvedDataSourceTree, request._explain === 'full')
                : { error: 'Unresolveable request' };
        }

        if (results.cursor) {
            response.cursor = results.cursor;
            if (response.cursor.totalCount === null) delete response.cursor.totalCount;
            response.cursor.limit = resolved.dataSourceTree.request.limit;
            response.cursor.page = resolved.dataSourceTree.request.page;

            if (response.cursor.limit && response.cursor.totalCount) {
                response.cursor.totalPages = Math.ceil(
                    response.cursor.totalCount / response.cursor.limit
                );
            }
        }

        return results.data;
    }
}

module.exports = ResourceProcessor;
<commit_msg>Use "finally" for profiler in resource-processor, fix orphan profiler instance<commit_after>'use strict';

const requestParser = require('flora-request-parser');
const has = require('has');

const configLoader = require('./config-loader');
const parseXml = require('./xml-reader');
const configParser = require('./config-parser');
const requestResolver = require('./request-resolver');
const dataSourceExecutor = require('./datasource-executor');
const resultBuilder = require('./result-builder');

function explainDataSourceTree(dst, full) {
    const explain = {};

    if (dst.subFilters) {
        explain.subFilters = dst.subFilters.map(
            subFilter => explainDataSourceTree(subFilter, full)
        );
    }

    // TODO: Move requestName generation into request-resolver:
    const requestName = dst.attributePath
        ? dst.attributePath.join('.') + ':' + dst.dataSourceName
        : 'unnamedRequest';

    explain.requestName = requestName;
    explain.type = dst.request.type;

    if (full) {
        explain.attributes = dst.request.attributes ? dst.request.attributes.join(',') : undefined;
        explain.filter = dst.request.filter
            ? dst.request.filter.map(orFilter => orFilter.map((andFilter) => {
                const operators = {
                    like: '~',
                    equal: '=',
                    notEqual: '!=',
                    less: '<',
                    lessOrEqual: '<=',
                    greater: '>',
                    greaterOrEqual: '>=',
                };

                function renderRecursiveArray(array) {
                    if (!Array.isArray(array)) return array;
                    return '[' + array.map(renderRecursiveArray).join(',') + ']';
                }

                return renderRecursiveArray(andFilter.attribute)
                    + (operators[andFilter.operator]
                        ? operators[andFilter.operator]
                        : ' ' + andFilter.operator + ' ')
                    + renderRecursiveArray(andFilter.value)
                    + (andFilter.valueFromParentKey ? '{from-parent-key}' : '')
                    + (has(andFilter, 'valueFromSubFilter') ? `{from-sub-filter: ${andFilter.valueFromSubFilter}}` : '');
            })
                .join(' AND '))
                .join(' OR ')
            : undefined;
        explain.search = dst.request.search;
        explain.order = dst.request.order
            ? dst.request.order.map(order => (order.attribute + ':' + order.direction)).join(',')
            : undefined;
        explain.limit = dst.request.limit;
        explain.page = dst.request.page;
    }

    if (dst.request._explain) {
        Object.assign(explain, dst.request._explain);
    } else {
        explain.executed = false;
    }

    if (dst.subRequests) {
        explain.subRequests = dst.subRequests.map(
            subRequest => explainDataSourceTree(subRequest, full)
        );
    }

    return explain;
}

/**
 * Provides a clean interface for DataSource configurations and their
 * relations.
 */
class ResourceProcessor {
    /**
     * @param {Api} api - The Api instance context.
     */
    constructor(api) {
        this.log = api.log.child({ component: 'resource-processor' });
        this.api = api;
        this.resourceConfigs = {};
    }

    /**
     * Initialize data sources.
     *
     * @param {Object} config - Configuration object
     * @param {Object} config.resourcePath - Path to resource files
     * @return {Promise}
     */
    async init(config = {}) {
        if (!config.resourcesPath) {
            this.log.warn('No resourcesPath in configuration, not loading any resources');
            return null;
        }

        this.log.debug('Loading configuration');

        const configs = await configLoader(this.api, {
            directory: config.resourcesPath,
            parsers: { xml: parseXml }
        });

        this.resourceConfigs = configs;
        this.log.debug('Parsing configs');
        configParser(this.resourceConfigs, this.api.dataSources);

        return this._initResources();
    }

    _initResources() {
        this.log.debug('Initializing resources');

        const resources = this.resourceConfigs;

        return Promise.all(Object.keys(resources).map(resourceName => new Promise((resolve) => {
            this.log.trace('Initializing resource "' + resourceName + '"');

            // no need for "init" extension for the default resource
            if (!resources[resourceName].instance) return resolve();

            // Extension: "init"
            const resource = resources[resourceName].instance;
            if (resource.extensions && resource.extensions.init && typeof resource.extensions.init === 'function') {
                this.log.trace('"init" extension (%s)', resourceName);
                resolve(resource.extensions.init());
            }

            return resolve();
        })));
    }

    /**
     * Handle a request.
     *
     * @param {Request} request - Request object
     * @param {Response} response - Response object (for modifying header information, etc.)
     * @return {Object}
     */
    async handle(request, response) {
        let resolvedDataSourceTree = null;

        // Extension: "request" (resource)
        this.log.trace('handle: "request" extensions (resource)');
        const resource = this.api.getResource(request.resource);
        if (resource && resource.extensions && resource.extensions.request) {
            this.api.log.trace('handle: "request" extension (%s)', request.resource);
            await resource.extensions.request({ request, response });
        }

        // requestParser
        this.log.trace('handle: requestParser');
        let profiler;
        let requestAST;
        profiler = request._profiler.child('requestParser');
        try {
            requestAST = requestParser.parse(request);
        } finally {
            profiler.end();
        }

        // requestResolver
        this.log.trace('handle: requestResolver');
        let resolved;
        profiler = request._profiler.child('requestResolver');
        try {
            resolved = requestResolver(requestAST, this.resourceConfigs);
            resolvedDataSourceTree = resolved.dataSourceTree;
        } finally {
            profiler.end();
        }

        let results;

        try {
            // dataSourceExecutor
            this.log.trace('handle: dataSourceExecutor');
            resolved.dataSourceTree._profiler = request._profiler.child('dataSourceExecutor');
            let rawResults;
            try {
                rawResults = await dataSourceExecutor(this.api, request, resolved.dataSourceTree);
            } finally {
                resolved.dataSourceTree._profiler.end();
            }

            // resultBuilder
            this.log.trace('handle: resultBuilder');
            profiler = request._profiler.child('resultBuilder');
            try {
                results = resultBuilder(this.api, request, rawResults, resolved.resolvedConfig);
            } finally {
                profiler.end();
            }
        } catch (err) {
            // explain even in case of an error
            if ((request._explain === '1' || request._explain === 'full') && this.api.config.allowExplain) {
                err.meta = err.meta || {};
                err.meta.explain = resolvedDataSourceTree
                    ? explainDataSourceTree(resolvedDataSourceTree, request._explain === 'full')
                    : { error: 'Unresolveable request' };
            }

            throw err;
        }

        if ((request._explain === '1' || request._explain === 'full') && this.api.config.allowExplain) {
            response.meta.explain = resolvedDataSourceTree
                ? explainDataSourceTree(resolvedDataSourceTree, request._explain === 'full')
                : { error: 'Unresolveable request' };
        }

        if (results.cursor) {
            response.cursor = results.cursor;
            if (response.cursor.totalCount === null) delete response.cursor.totalCount;
            response.cursor.limit = resolved.dataSourceTree.request.limit;
            response.cursor.page = resolved.dataSourceTree.request.page;

            if (response.cursor.limit && response.cursor.totalCount) {
                response.cursor.totalPages = Math.ceil(
                    response.cursor.totalCount / response.cursor.limit
                );
            }
        }

        return results.data;
    }
}

module.exports = ResourceProcessor;
<|endoftext|>