/** * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS-IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // appjetContext.cache_requestCache()._t_start = (new Date()).valueOf(); var _appjethidden_ = {}; var serverhandlers = { tasks: {} }; /* * @overview * * AppJet standard library preamble. * * This is run at the beginning of every request, right after all * native calls are loaded into appjetContext. This file is run * in the same scope as the app, the global scope, which is also * accessible from all modules. */ //---------------------------------------------------------------- // delete pesky rhino built-in string stuff //---------------------------------------------------------------- (function() { // rhino strings come with a bunch of random "html helpers" // that we don't want var htmlStuff = ["bold", "italics", "fixed", "strike", "small", "big", "sub", "fontsize", "fontcolor", "link", "anchor", "sup", "blink"]; for(var i in htmlStuff) { delete String.prototype[htmlStuff[i]]; } })(); //---------------------------------------------------------------- // module implementation //---------------------------------------------------------------- (function(globalScope) { //---------------------------------------------------------------- // Utility Functions //---------------------------------------------------------------- function appjetContext() { return net.appjet.oui.ExecutionContextUtils.currentContext(); } function internalError(m) { throw new Error("AppJet Internal Error: "+m); } function apiError(m) { throw new Error("AppJet API Error: "+m); } function newScope() { var o = new Object(); o.__parent__ = null; o.__proto__ = globalScope; return o; } _appjethidden_._debugMessage = function(m) { //java.lang.System.out.println(m); }; var debug = _appjethidden_._debugMessage; function copySymbol(srcName, symName, src, dst, dstSymName) { if (!src.hasOwnProperty(symName)) { apiError("Import error: module \""+srcName+"\" does not contain the symbol \""+symName+"\"."); } if (symName.charAt(0) == '_') { apiError("Import error: cannot import symbol \""+symName+"\" because it is private (begins with _)"); } debug(" | copying symbol ["+symName+"]"); dst[dstSymName || symName] = src[symName]; } function copyPublicSymbols(src, dst) { for (k in src) { if (src.hasOwnProperty(k) && (k.length > 0) && (k.charAt(0) != '_')) { copySymbol('', k, src, dst); } } } // Module import cache... hidden from other scopes. var moduleObjects = {}; var modulesBeingLoaded = {}; /*-------------------------------------------------------------------------------- * loadModule(): * Evaluates moduleName in its own private scope, then copies its public identifiers * into a new scope. This new scope is stored in moduleObjects[moduleName] for future use * by import()s. * * If moduleName is currently being loaded (because we are in the middle of another loadModule() * higher in the call stack), then this function does noething, on the assumption * that moduleName will eventually be loaded anyway. Therefore, it cannot be assumed that * moduleName is done being loaded when loadModule() returns, only that it eventually will be * loaded when all loadModule calls return up the call stack. *--------------------------------------------------------------------------------*/ function loadModule(moduleName) { if (modulesBeingLoaded[moduleName]) { // This is OK. The module will be loaded eventually. return; } if (moduleObjects[moduleName]) { return; } modulesBeingLoaded[moduleName] = true; try { debug("loadModule: "+moduleName); var modulePrivateScope = Packages.net.appjet.ajstdlib.ajstdlib.runModuleInNewScope( appjetContext(), moduleName.split('.').join('/')); if (!modulePrivateScope) { // moduleName is not a module. This is normal, because when someone calls // import("foo.bar"), we dont know if bar is a module or an identifier in the foo module. delete modulesBeingLoaded[moduleName]; return; } // Thinking this could be useful: // modulePrivateScope['__MODULE_NAME__'] = moduleName; var moduleObj = newScope(); copyPublicSymbols(modulePrivateScope, moduleObj); moduleObjects[moduleName] = moduleObj; } finally { delete modulesBeingLoaded[moduleName]; } } /*-------------------------------------------------------------------------------- * importSingleModule(): * * Takes a single moduleName (like "etherpad.foo.bar.baz") and creates the identifier "baz" * in dstScope, referencing the module etherpad.foo.bar.baz. * * This function is called one or more times by importPath(). Note that importPath() is more like * the import() function that modules ses. *--------------------------------------------------------------------------------*/ function importSingleModule(moduleName, dstScope) { debug("importSingleModule: "+moduleName); if (typeof(moduleName) != 'string') { apiError("modules should be referred to with string, not "+typeof(moduleName)); } var moduleObj = moduleObjects[moduleName]; // public module scope if (!moduleObj) { return false; } var importedName = moduleName; if (importedName.indexOf(".") != -1) { importedName = importedName.split(".").slice(-1)[0]; } dstScope[importedName] = moduleObj; return true; } /*-------------------------------------------------------------------------------- * importPath(): * takes a modulePath (like "a.b.c.{d,e,f}" or "a.b.*" or just "a.b" or "a") and * repeatedly calls importSingleModule() as necessary, copying public symbols into dst. *--------------------------------------------------------------------------------*/ function importPath(modulePath, dst) { debug("importPath: "+modulePath); // Two possibilties: // 1. import the exact module and that's it. // // 2. module contains a "." and we need to import up to the // last ., and then import a name (or set of names) from it. // first try case 1: var ok = importSingleModule(modulePath, dst); if (ok) { return; } if (modulePath.indexOf(".") == -1) { throw new Error("Module does not exist: "+modulePath); } // now try case 2: var tempDst = newScope(); var moduleName = modulePath.split('.').slice(0, -1).join('.'); var importedName = modulePath.split('.').slice(-1)[0]; var lastName = modulePath.split('.').slice(-2, -1)[0]; ok = importSingleModule(moduleName, tempDst); if (!ok) { throw new Error("Neither module exists: "+moduleName+", "+modulePath); } if (!tempDst[lastName]) { internalError("import failed for "+moduleName+"|"+importedName+". This could be an appjet bug."); } if (importedName == "*") { copyPublicSymbols(tempDst[lastName], dst); } else if (importedName.match(/^\{.*\}$/)) { importedName.slice(1,-1).split(',').forEach(function(sym) { if (sym.match(/^.*=>.*$/)) { copySymbol(moduleName, sym.split("=>")[0], tempDst[lastName], dst, sym.split("=>")[1]); } else { copySymbol(moduleName, sym, tempDst[lastName], dst); } }); } else { copySymbol(moduleName, importedName, tempDst[lastName], dst); } } //---------------------------------------------------------------- // scheduling //---------------------------------------------------------------- var scheduledImports = []; function scheduleImportPath(p, dst) { scheduledImports.push([p, dst]); } function runScheduledImports() { scheduledImports.forEach(function(x) { importPath(x[0], x[1]); }); } //---------------------------------------------------------------- // The global import function //---------------------------------------------------------------- _appjethidden_.importsAllowed = true; globalScope['import'] = function(path1, path2, etc) { if (!_appjethidden_.importsAllowed) { throw Error("Imports are finished. No more imports are allowed."); } var dstScope = this; if (arguments.length < 1) { apiError("importModule() takes the name of at least one module as an argument."); } for (var i = 0; i < arguments.length; i++) { var path = arguments[i]; debug("scheduling import: "+path); scheduleImportPath(path, dstScope); // evaluate all modules in this path. var parts = path.split('.'); for (var j = 0; j < parts.length; j++) { var moduleName = parts.slice(0,j+1).join('.'); loadModule(moduleName); } } }; _appjethidden_.finishImports = function() { debug("Running scheduled imports..."); runScheduledImports(); _appjethidden_.importsAllowed = false; }; //---------------------------------------------------------------- // jimport //---------------------------------------------------------------- function _jimportSinglePackage(pname, dstScope) { //_appjethidden_._debugMessage("_jimportSinglePackage: "+pname); // TODO: support "*" and "{}" syntax like scala. var src = Packages; var srcParent = null; var localName = pname.split(".").pop(); var soFar = ''; pname.split(".").forEach(function(x) { soFar += x+'.'; if (!src[x]) { throw ('Could not find java package/class: '+soFar); } else { //_appjethidden_._debugMessage("descenting into "+src+"["+x+"]"); srcParent = src; src = src[x]; } }); if (String(src).indexOf('function') == 0) { // TODO: checking String(src).indexOf('function') is rather brittle. // is there a cleaner way? // TODO: this only works on static functions... so make sure // src[x] is a static function! dstScope[localName] = function() { return src.apply(srcParent, Array.prototype.slice.call(arguments)); }; } else { // importing a regular java class dstScope[localName] = src; } } /** * Import a java package over LiveConnect. */ globalScope['jimport'] = function() { var dstScope = this; for (var i = 0; i < arguments.length; i++) { var pname = arguments[i].split(".").pop(); _jimportSinglePackage(arguments[i], dstScope); } }; //---------------------------------------------------------------- // {appjet, request, response} imported by default //---------------------------------------------------------------- globalScope['import'].call(globalScope, "global.appjet.appjet", "global.request.request", "global.response.response"); })(this);