database-jones/Adapter/api/jones.js (239 lines of code) (raw):

/* Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ "use strict"; var path = require("path"), fs = require("fs"), assert = require("assert"), util = require("util"), conf = require("../adapter_config"), UserContext = null, // loaded later to prevent circular dependency unified_debug = require("unified_debug"), stats_module = require("./stats"), udebug = unified_debug.getLogger("jones.js"), existsSync = fs.existsSync || path.existsSync, privateModuleRegistry = {}, deploymentSearchPath = [], resolvedDeployments = {}; function common(file) { return path.join(conf.spi_common_dir, file); } function api_dir(file) { return path.join(conf.api_dir, file); } function api_doc_dir(file) { return path.join(conf.api_doc_dir, file); } function spi_doc_dir(file) { return path.join(conf.spi_doc_dir, file); } function converter(file) { return path.join(conf.converters_dir, file); } exports.common = { "BitMask" : common("BitMask"), "DBTableHandler" : common("DBTableHandler"), "IndexBounds" : common("IndexBounds"), "QueuedAsyncCall" : common("QueuedAsyncCall"), "MySQLTime" : common("MySQLTime"), "SQLBuilder" : common("SQLBuilder"), "SQLTransactionHandler" : common("SQLTransactionHandler"), "FieldValueDefinedListener" : common("FieldValueDefinedListener"), "DictionaryCall" : common("DictionaryCall"), "MySQLSerialize" : common("MySQLSerialize") }; exports.api = { "TableMapping" : api_dir("TableMapping"), "stats" : api_dir("stats"), "UserContext" : api_dir("UserContext"), "Meta" : api_dir("Meta") }; exports.spi_doc = { "DBOperation" : spi_doc_dir("DBOperation"), "DBConnectionPool" : spi_doc_dir("DBConnectionPool"), "DBServiceProvider" : spi_doc_dir("DBServiceProvider"), "DBTransactionHandler" : spi_doc_dir("DBTransactionHandler") }; exports.api_doc = { "TableMetadata" : api_doc_dir("TableMetadata"), "TableMapping" : api_doc_dir("TableMapping"), "Jones" : api_doc_dir("Jones") }; exports.meta = require(exports.api.Meta); exports.fs = conf; // Export path helpers under jones.fs exports.TableMapping = require("./TableMapping").TableMapping; exports.Projection = require("./Projection").Projection; exports.stats = stats_module; stats_module.register(resolvedDeployments, "api", "deployments", "resolved"); stats_module.register(deploymentSearchPath, "api", "deployments", "searchPath"); /* getDBServiceProviderModule() The usual way to load SPI module "x" is to require("jones-x"). For bootstrapping during development of x, though, it is possible to use jones.registerDBServiceProvider("x", x_module); getDBServiceProviderModule() will then return the registered object. */ function getDBServiceProviderModule(impl_name) { var externalModule, service; externalModule = "jones-" + impl_name; service = privateModuleRegistry[impl_name]; if(! service) { try { service = require(externalModule); } catch(e) { console.log("Cannot load module " + externalModule); throw e; } } return service; } exports.registerDBServiceProvider = function(name, module) { privateModuleRegistry[name] = module; }; function getDBServiceProvider(impl_name) { var service = getDBServiceProviderModule(impl_name); /* Now verify that the module can load its dependencies. This will throw an exception if it fails. */ service.loadRequiredModules(); return service; } exports.getDBServiceProvider = getDBServiceProvider; exports.converters = { "JSONConverter" : require(converter("JSONConverter")), "NumericConverter" : require(converter("NumericConverter")), "SerializedObjectConverter" : require(converter("SerializedObjectConverter")) }; /* Build the jones_deployment.js search path. This is done once at startup time. */ function buildSearchPath() { var mod, sourceDir, oldDir; assert(deploymentSearchPath.length === 0); // (1) Look in the same directory as the main JS script (require.main) // (2) Walk the chain of required modules from the main script towards jones.js mod = module; // node.js file-scope "module" do { sourceDir = path.dirname(mod.filename); deploymentSearchPath.unshift(sourceDir); mod = mod.parent; } while(mod); // (3) Walk the filesystem from the main script to the root directory // TODO: Test this on Windows sourceDir = path.dirname(sourceDir); while(oldDir !== sourceDir) { // these become equal at "/" deploymentSearchPath.push(sourceDir); oldDir = sourceDir; sourceDir = path.dirname(sourceDir); } // (4) Finally look in the current working directory if(deploymentSearchPath.indexOf(process.env.PWD) < 0) { deploymentSearchPath.push(process.env.PWD); } } buildSearchPath(); // once at startup function DeploymentPathIterator() { this.index = 0; } DeploymentPathIterator.prototype.next = function() { var file; while(this.index < deploymentSearchPath.length) { file = path.join(deploymentSearchPath[this.index++], "jones_deployments.js"); if(existsSync(file)) { return file; } } // fall through, return undefined }; function findNamedDeployment(deploymentName) { var iter, deploymentFile, deploymentModule, deploymentFn; iter = new DeploymentPathIterator(); while((deploymentFile = iter.next()) !== undefined) { deploymentModule = require(deploymentFile); deploymentFn = deploymentModule[deploymentName]; if(typeof deploymentFn === 'function') { resolvedDeployments[deploymentName] = deploymentFile; // global stat return deploymentFn; } } assert(false, "Named deployment " + deploymentName + " not found in " + deploymentSearchPath.join(":")); } function getDeploymentsFunction(deployment) { switch(typeof deployment) { case 'string': return findNamedDeployment(deployment); case 'function': return deployment; default: assert(false, "deployment must be a string or function"); } } function mergeSuppliedProperties(newProperties, propertiesToMerge) { var key; for(key in propertiesToMerge) { if(propertiesToMerge.hasOwnProperty(key)) { newProperties[key] = propertiesToMerge[key]; } } } function cloneDefaultPropertiesForServiceProvider(impl) { var serviceProvider, defaultProperties; /* Fetch the default connection properties */ serviceProvider = getDBServiceProviderModule(impl); defaultProperties = serviceProvider.getDefaultConnectionProperties(); /* Sanity Check */ assert.strictEqual(defaultProperties.implementation, impl, "invalid implementation name in default connection properties"); /* Clone them */ return JSON.parse(JSON.stringify(defaultProperties)); } exports.ConnectionProperties = function(nameOrProperties, deployment) { var impl, properties, deploymentFn; if(typeof nameOrProperties === 'string') { impl = nameOrProperties; properties = cloneDefaultPropertiesForServiceProvider(impl); } else if(typeof nameOrProperties === 'object') { impl = nameOrProperties.implementation; if(impl) { properties = cloneDefaultPropertiesForServiceProvider(impl); mergeSuppliedProperties(properties, nameOrProperties); } else { properties = nameOrProperties; } } else { assert.ok(false, "ConnectionProperties() first parameter must be an adapter name or properties object"); } udebug.log("ConnectionProperties", impl || "impl deferred to deployment"); /* Apply deployment */ if(deployment !== undefined) { deploymentFn = getDeploymentsFunction(deployment); properties = deploymentFn(properties) || properties; // Use properties if returned } /* After applying deployment, check that properties.implementation is set and that the DBServiceProvider module can be loaded */ impl = properties.implementation; assert.equal(typeof impl, 'string', "Properties object must include implementation name"); getDBServiceProviderModule(impl); /* "Normally constructors don't return a value, but they can choose to" */ udebug.log_detail(properties); return properties; }; function requireUserContext() { if(! UserContext) { UserContext = require("./UserContext").UserContext; } } /** Determine if a parameter is a possible mappings. Possible mappings include null, undefined, * array, string, mapped constructor function, or TableMapping object. * @param fn possible mapping * @return true if the parameter may be a mappings */ function isMappings(fn) { if (fn === null) {return true;} if (fn === undefined) {return true;} if (Array.isArray(fn)) {return true;} if (typeof fn === 'string') {return true;} if (typeof fn === 'function') { if (fn.prototype.jones) { return true; } } if (typeof fn === 'object') { if (fn.constructor && fn.constructor.name === 'TableMapping') { return true; } } return false; } /** Methods implemented in UserContext **/ exports.connect = function(properties, mappings, user_callback) { // copy arguments so they can be changed // if optional annotations argument is skipped, set it to null and set callback from second argument var args = arguments; if (arguments.length == 2 && !isMappings(mappings)) { args[2] = mappings; args[1] = null; } requireUserContext(); var context = new UserContext(args, 3, 2, null, null); context.cacheTableHandlerInSessionFactory = true; return context.connect(); }; exports.openSession = function(properties, mappings, user_callback) { // copy arguments so they can be changed // if optional annotations argument is skipped, set it to null and set callback from second argument var args = arguments; if (arguments.length == 2 && !isMappings(mappings)) { args[2] = mappings; args[1] = null; } requireUserContext(); var context = new UserContext(args, 3, 2, null, null); context.cacheTableHandlerInSession = true; return context.openSession(); }; exports.getOpenSessionFactories = function() { requireUserContext(); var context = new UserContext(arguments, 0, 0); return context.getOpenSessionFactories(); }; exports.closeAllOpenSessionFactories = function() { requireUserContext(); var context = new UserContext(arguments, 1, 1); return context.closeAllOpenSessionFactories(); }; exports.isMappings = isMappings;