/** This code borrowed from Piesquared on irc.nexuswar.com */ private TimeZone tZF(String search) { if (search.equals("")) return TimeZone.getTimeZone("GMT"); search = search.toLowerCase(); search = search.replace("+", "\\+"); search = search.replace("*", "\\*"); search = search.replace("?", "\\?"); String[] zones = TimeZone.getAvailableIDs(); for (int i = 0; i < zones.length; i++) if (zones[i].toLowerCase().equals(search)) return TimeZone.getTimeZone(zones[i]); for (int i = 0; i < zones.length; i++) if (zones[i].toLowerCase().matches(".*" + search + ".*")) return TimeZone.getTimeZone(zones[i]); return TimeZone.getTimeZone("GMT"); }
private XMLGregorianCalendar getTimeToXmlTime(long lastModified) throws DatatypeConfigurationException { GregorianCalendar gc = new GregorianCalendar(TimeZone.getTimeZone("UTC")); gc.setTime(new Date(lastModified)); XMLGregorianCalendar xmlTime = DatatypeFactory.newInstance().newXMLGregorianCalendar(gc); return xmlTime; }
public String _tstamp(String args[]) { String format = "yyyyMMddHHmm"; long now = System.currentTimeMillis(); TimeZone tz = TimeZone.getTimeZone("UTC"); if (args.length > 1) { format = args[1]; } if (args.length > 2) { tz = TimeZone.getTimeZone(args[2]); } if (args.length > 3) { now = Long.parseLong(args[3]); } if (args.length > 4) { domain.warning("Too many arguments for tstamp: " + Arrays.toString(args)); } SimpleDateFormat sdf = new SimpleDateFormat(format); sdf.setTimeZone(tz); return sdf.format(new Date(now)); }
static { String pattern = "EEE, dd MMM yyyy HH:mm:ss zzz"; tz = TimeZone.getTimeZone("GMT"); df = new SimpleDateFormat(pattern, Locale.US); df.setTimeZone(tz); }
/** * This is the container for an instance of a site on a single server. This can be access via * __instance__ * * @anonymous name : {local}, isField : {true}, desc : {Refers to the site being run.}, type: * {library} * @anonymous name : {core}, isField : {true}, desc : {Refers to corejs.} example : * {core.core.mail() calls corejs/core/mail.js}, type : {library} * @anonymous name : {external} isField : {true}, desc : {Refers to the external libraries.}, type : * {library} * @anonymous name : {db}, isField : {true}, desc : {Refers to the database.}, type : {database} * @anonymous name : {setDB} desc : {changes <tt>db</tt> to refer to a different database.} param : * {type : (string) name : (dbname) desc : (name of the database to which to connect)} * @anonymous name : {SYSOUT} desc : {Prints a string.} param : {type : (string) name : (str) desc : * (the string to print)} * @anonymous name : {log} desc : {Global logger.} param : {type : (string) name : (str) desc : (the * string to log)} * @expose * @docmodule system.system.__instance__ */ public class AppContext extends ServletContextBase implements JSObject, Sizable { /** @unexpose */ static final boolean DEBUG = AppServer.D; /** * If these files exist in the directory or parent directories of a file being run, run these * files first. Includes _init.js and /~~/core/init.js. */ static final String INIT_FILES[] = new String[] {"/~~/core/init.js", "PREFIX_init"}; /** * Initializes a new context for a given site directory. * * @param f the file to run */ public AppContext(File f) { this(f.toString()); } /** * Initializes a new context for a given site's path. * * @param root the path to the site from where ed is being run */ public AppContext(String root) { this(root, guessNameAndEnv(root).name, guessNameAndEnv(root).env); } /** * Initializes a new context. * * @param root the path to the site * @param name the name of the site * @param environment the version of the site */ public AppContext(String root, String name, String environment) { this(root, new File(root), name, environment); } /** * Initializes a new context. * * @param root the path to the site * @param rootFile the directory in which the site resides * @param name the name of the site * @param environment the version of the site */ public AppContext(String root, File rootFile, String name, String environment) { this(root, rootFile, name, environment, null); } private AppContext( String root, File rootFile, String name, String environment, AppContext nonAdminParent) { super(name + ":" + environment); if (root == null) throw new NullPointerException("AppContext root can't be null"); if (rootFile == null) throw new NullPointerException("AppContext rootFile can't be null"); if (name == null) name = guessNameAndEnv(root).name; if (name == null) throw new NullPointerException("how could name be null"); _root = root; _rootFile = rootFile; _git = new GitDir(_rootFile); _name = name; _environment = environment; _nonAdminParent = nonAdminParent; _admin = _nonAdminParent != null; _codePrefix = _admin ? "/~~/modules/admin/" : ""; _moduleRegistry = ModuleRegistry.getNewGlobalChild(); if (_git.isValid()) { _gitBranch = _git.getBranchOrTagName(); _gitHash = _git.getCurrentHash(); } _isGrid = name.equals("grid"); _scope = new Scope( "AppContext:" + root + (_admin ? ":admin" : ""), _isGrid ? ed.cloud.Cloud.getInstance().getScope() : Scope.newGlobal(), null, Language.JS(), _rootFile); _scope.setGlobal(true); _initScope = _scope.child("_init"); _usage = new UsageTracker(this); _baseScopeInit(); _adminContext = _admin ? null : new AppContext(root, rootFile, name, environment, this); _rootContextReachable = new SeenPath(); if (!_admin) _logger.info( "Started Context. root:" + _root + " environment:" + environment + " git branch: " + _gitBranch); } /** * Returns the adapter type for the given file. Will first use the adapter selector function if it * was specified in init.js, otherwise will use the static type (either set in _init file, as a * server-wide override in 10gen.properties, or default of DIRECT_10GEN) * * @param file to produce type for * @return adapter type for the specified file */ public AdapterType getAdapterType(File file) { // Q : I think this is the right thing to do if (inScopeSetup()) { return AdapterType.DIRECT_10GEN; } /* * cheap hack - prevent any _init.* file from getting run as anythign but DIRECT_10GEN */ if (file != null && file.getName().indexOf("_init.") != -1) { return AdapterType.DIRECT_10GEN; } if (_adapterSelector == null) { return _staticAdapterType; } /* * only let the app select type if file is part of application (i.e. * don't do it for corejs, core modules, etc... */ String fp = file.getAbsolutePath(); String fullRoot = _rootFile.getAbsolutePath(); // there must be a nicer way to do this? if (!fp.startsWith(fullRoot)) { return AdapterType.DIRECT_10GEN; } Object o = _adapterSelector.call(_initScope, new JSString(fp.substring(fullRoot.length()))); if (o == null) { return _staticAdapterType; } if (!(o instanceof JSString)) { log("Error : adapter selector not returning string. Ignoring and using static adapter type"); return _staticAdapterType; } AdapterType t = getAdapterTypeFromString(o.toString()); return (t == null ? _staticAdapterType : t); } /** * Creates a copy of this context. * * @return an identical context */ AppContext newCopy() { return new AppContext(_root, _rootFile, _name, _environment, _nonAdminParent); } /** Initializes the base scope for the application */ private void _baseScopeInit() { // --- libraries if (_admin) _scope.put("local", new JSObjectBase(), true); else _setLocalObject(new JSFileLibrary(_rootFile, "local", this)); _loadConfig(); _core = CoreJS.get().getLibrary(getCoreJSVersion(), this, null, true); _logger.info("corejs : " + _core.getRoot()); _scope.put("core", _core, true); _external = Module.getModule("external").getLibrary(getVersionForLibrary("external"), this, null, true); _scope.put("external", _external, true); _scope.put("__instance__", this, true); _scope.lock("__instance__"); // --- db if (!_isGrid) { _scope.put("db", DBProvider.get(this), true); _scope.put( "setDB", new JSFunctionCalls1() { public Object call(Scope s, Object name, Object extra[]) { if (name.equals(_lastSetTo)) return true; DBBase db = (DBBase) AppContext.this._scope.get("db"); if (!db.allowedToAccess(name.toString())) throw new JSException("you are not allowed to access db [" + name + "]"); if (name.equals(db.getName())) return true; AppContext.this._scope.put( "db", DBProvider.get(AppContext.this, name.toString()), false); _lastSetTo = name.toString(); if (_adminContext != null) { // yes, i do want a new copy so Constructors don't get copied for both _adminContext._scope.put( "db", DBProvider.get(AppContext.this, name.toString()), false); } return true; } String _lastSetTo = null; }, true); } // --- output _scope.put( "SYSOUT", new JSFunctionCalls1() { public Object call(Scope s, Object str, Object foo[]) { System.out.println(AppContext.this._name + " \t " + str); return true; } }, true); _scope.put("log", _logger, true); // --- random? _scope.put( "openFile", new JSFunctionCalls1() { public Object call(Scope s, Object name, Object extra[]) { return new JSLocalFile(_rootFile, name.toString()); } }, true); _scope.put("globalHead", _globalHead, true); Map<String, JSFileLibrary> rootFileMap = new HashMap<String, JSFileLibrary>(); for (String rootKey : new String[] {"local", "core", "external"}) { Object temp = _scope.get(rootKey); if (temp instanceof JSFileLibrary) rootFileMap.put(rootKey, (JSFileLibrary) temp); } _scope.put( "fork", new JSFunctionCalls1() { public Object call(final Scope scope, final Object funcJS, final Object extra[]) { if (!(funcJS instanceof JSFunction)) throw new JSException("fork has to take a function"); return queueWork("forked", (JSFunction) funcJS, extra); } }); _scope.lock("fork"); ed.appserver.templates.djang10.JSHelper.install(_scope, rootFileMap, _logger); _scope.lock("user"); // protection against global user object } private void _loadConfig() { try { _configScope.set("__instance__", this); _loadConfigFromCloudObject(getSiteObject()); _loadConfigFromCloudObject(getEnvironmentObject()); File f; if (!_admin) { f = getFileSafe("_config.js"); if (f == null || !f.exists()) f = getFileSafe("_config"); } else f = new File( Module.getModule("core-modules/admin").getRootFile(getVersionForLibrary("admin")), "_config.js"); _libraryLogger.info("config file [" + f + "] exists:" + f.exists()); if (f == null || !f.exists()) return; Convert c = new Convert(f); JSFunction func = c.get(); func.setUsePassedInScope(true); func.call(_configScope); _logger.debug("config things " + _configScope.keySet()); } catch (Exception e) { throw new RuntimeException("couldn't load config", e); } } private void _loadConfigFromCloudObject(JSObject o) { if (o == null) return; _configScope.putAll((JSObject) o.get("config")); } /** * Get the version of corejs to run for this AppContext. * * @return the version of corejs as a string. null if should use default */ public String getCoreJSVersion() { Object o = _scope.get("corejsversion"); if (o != null) { _logger.error("you are using corejsversion which is deprecated. please use version.corejs"); return JS.toString(o); } return getVersionForLibrary("corejs"); } /** * Get the version of a library to run. * * @param name the name of the library to look up * @return the version of the library to run as a string. null if should use default */ public String getVersionForLibrary(String name) { String version = getVersionForLibrary(_configScope, name, this); _libraryVersions.set(name, version); return version; } public JSObject getLibraryVersionsLoaded() { return _libraryVersions; } /** @unexpose */ public static String getVersionForLibrary(Scope s, String name) { AppRequest ar = AppRequest.getThreadLocal(); return getVersionForLibrary(s, name, ar == null ? null : ar.getContext()); } /** @unexpose */ private static String getVersionForLibrary(Scope s, String name, AppContext ctxt) { final String version = _getVersionForLibrary(s, name, ctxt); _libraryLogger.log( ctxt != null && !ctxt._admin ? Level.DEBUG : Level.INFO, ctxt + "\t" + name + "\t" + version); return version; } private static String _getVersionForLibrary(Scope s, String name, AppContext ctxt) { final JSObject o1 = ctxt == null ? null : (JSObject) (s.get("version_" + ctxt.getEnvironmentName())); final JSObject o2 = (JSObject) s.get("version"); _libraryLogger.debug(ctxt + "\t versionConfig:" + (o1 != null) + " config:" + (o2 != null)); String version = _getString(name, o1, o2); if (version != null) return version; if (ctxt == null || ctxt._nonAdminParent == null) return null; return ctxt._nonAdminParent.getVersionForLibrary(name); } private static String _getString(String name, JSObject... places) { for (JSObject o : places) { if (o == null) continue; Object temp = o.get(name); if (temp == null) continue; return temp.toString(); } return null; } /** @return [ <name> , <env> ] */ static NameAndEnv guessNameAndEnv(String root) { root = ed.io.FileUtil.clean(root); root = root.replaceAll("\\.+/", ""); String pcs[] = root.split("/+"); if (pcs.length == 0) throw new RuntimeException("no root for : " + root); // handle anything with sites/foo for (int i = 0; i < pcs.length - 1; i++) if (pcs[i].equals("sites")) { return new NameAndEnv(pcs[i + 1], i + 2 < pcs.length ? pcs[i + 2] : null); } final int start = pcs.length - 1; for (int i = start; i > 0; i--) { String s = pcs[i]; if (i == start && (s.equals("master") || s.equals("test") || s.equals("www") || s.equals("staging") || // s.equals("stage") || s.equals("dev"))) continue; return new NameAndEnv(s, i + 1 < pcs.length ? pcs[i + 1] : null); } return new NameAndEnv(pcs[0], pcs.length > 1 ? pcs[1] : null); } static class NameAndEnv { NameAndEnv(String name, String env) { this.name = name; this.env = env; } final String name; final String env; } /** * Returns the name of the site being run. * * @return the name of the site */ public String getName() { return _name; } /** * Get the database being used. * * @return The database being used */ public DBBase getDB() { return (DBBase) _scope.get("db"); } /** * Given the _id of a JSFile, return the file. * * @param id _id of the file to find * @return The file, if found, otherwise null */ JSFile getJSFile(String id) { if (id == null) return null; DBCollection f = getDB().getCollection("_files"); return (JSFile) (f.find(new ObjectId(id))); } /** * Returns (and if necessary, reinitializes) the scope this context is using. * * @return the scope */ public Scope getScope() { return _scope(); } public Scope getInitScope() { return _initScope; } public Object getInitObject(String what) { return getFromInitScope(what); } public Object getFromInitScope(String what) { if (!_knownInitScopeThings.contains(what)) System.err.println("*** Unknown thing requested from initScope [" + what + "]"); return _initScope.get(what); } public void setInitObject(String name, Object value) { _initScope.set(name, value); } void setTLPreferredScope(AppRequest req, Scope s) { _scope.setTLPreferred(s); } private synchronized Scope _scope() { if (_inScopeSetup) return _scope; if (_getScopeTime() > _lastScopeInitTime) _scopeInited = false; if (_scopeInited) return _scope; _scopeInited = true; _lastScopeInitTime = System.currentTimeMillis(); _setupScope(); _setStaticAdapterType(); _setAdapterSelectorFunction(); return _scope; } protected void _setAdapterSelectorFunction() { Object o = this.getFromInitScope(INIT_ADAPTER_SELECTOR); if (o == null) { log("Adapter selector function not specified in _init file"); return; } if (!(o instanceof JSFunction)) { log( "Adapter selector function specified in _init file not a function. Ignoring. [" + o.getClass() + "]"); return; } _adapterSelector = (JSFunction) o; log("Adapter selector function specified in _init file"); } public void setStaticAdapterTypeValue(AdapterType type) { log("Static adapter type directly set : " + type); _staticAdapterType = type; } public AdapterType getStaticAdapterTypeValue() { return _staticAdapterType; } /** * Figure out what kind of static adapter type was specified. By default it's a 10genDEFAULT app */ protected void _setStaticAdapterType() { /* * app configuration steps could have set this already. If so, don't bother doing anything */ if (_staticAdapterType != AdapterType.UNSET) { log("Static adapter type has already been directly set to " + _staticAdapterType); return; } /* * check to see if overridden in 10gen.properties */ String override = Config.get().getProperty(INIT_ADAPTER_TYPE); if (override != null) { AdapterType t = getAdapterTypeFromString(override); if (t == null) { log( "Static adapter type specified as override [" + override + "] unknown - will use _init file specified or default"); } else { log("Static adapter type overridden by 10gen.properties or env. Value : " + override); _staticAdapterType = t; return; } } /* * if not, use the one from _init file if specified */ _staticAdapterType = AdapterType.DIRECT_10GEN; Object o = getFromInitScope(INIT_ADAPTER_TYPE); if (o == null) { log("Static adapter type not specified in _init file - using default value of DIRECT_10GEN"); return; } if (!(o instanceof JSString)) { log("Static adapter type from _init file not a string - using default value of DIRECT_10GEN"); return; } _staticAdapterType = getAdapterTypeFromString(o.toString()); if (_staticAdapterType == null) { log( "Static adapter type from _init file [" + o.toString() + "] unknown - using default value of DIRECT_10GEN"); _staticAdapterType = AdapterType.DIRECT_10GEN; return; } log("Static adapter type specified in _init file = " + _staticAdapterType); return; } public AdapterType getAdapterTypeFromString(String s) { if (AdapterType.DIRECT_10GEN.toString().equals(s.toUpperCase())) { return AdapterType.DIRECT_10GEN; } if (AdapterType.CGI.toString().equals(s.toUpperCase())) { return AdapterType.CGI; } if (AdapterType.WSGI.toString().equals(s.toUpperCase())) { return AdapterType.WSGI; } return null; } /** @unexpose */ public File getFileSafe(final String uri) { try { return getFile(uri); } catch (FileNotFoundException fnf) { return null; } } /** @unexpose */ public File getFile(final String uri) throws FileNotFoundException { File f = _files.get(uri); if (f != null) return f; if (uri.startsWith("/~~/") || uri.startsWith("~~/")) f = _core.getFileFromPath(uri.substring(3)); else if (uri.startsWith("/admin/")) f = _core.getFileFromPath("/modules" + uri); else if (uri.startsWith("/@@/") || uri.startsWith("@@/")) f = _external.getFileFromPath(uri.substring(3)); else if (_localObject != null && uri.startsWith("/modules/")) f = _localObject.getFileFromPath(uri); else f = new File(_rootFile, uri); if (f == null) throw new FileNotFoundException(uri); _files.put(uri, f); return f; } public String getRealPath(String path) { try { return getFile(path).getAbsolutePath(); } catch (FileNotFoundException fnf) { throw new RuntimeException("file not found [" + path + "]"); } } public URL getResource(String path) { try { File f = getFile(path); if (!f.exists()) return null; return f.toURL(); } catch (FileNotFoundException fnf) { // the spec says to return null if we can't find it // even though this is weird... return null; } catch (IOException ioe) { throw new RuntimeException("error opening [" + path + "]", ioe); } } public InputStream getResourceAsStream(String path) { URL url = getResource(path); if (url == null) return null; try { return url.openStream(); } catch (IOException ioe) { throw new RuntimeException("can't getResourceAsStream [" + path + "]", ioe); } } /** * This causes the AppContext to be started over. All context level variable will be lost. If code * is being managed, will cause it to check that its up to date. */ public void reset() { _reset = true; } /** Checks if this context has been reset. */ public boolean isReset() { return _reset; } /** * Returns the path to the directory the appserver is running. (For example, site/version.) * * @return the path */ public String getRoot() { return _root; } public File getRootFile() { return _rootFile; } /** * Creates an new request for the app server from an HTTP request. * * @param request HTTP request to create * @return the request */ public AppRequest createRequest(HttpRequest request) { return createRequest(request, request.getHost(), request.getURI()); } /** * Creates an new request for the app server from an HTTP request. * * @param request HTTP request to create * @param uri the URI requested * @return the request */ public AppRequest createRequest(HttpRequest request, String host, String uri) { _numRequests++; if (AppRequest.isAdmin(request)) return new AppRequest(_adminContext, request, host, uri); return new AppRequest(this, request, host, uri); } /** * Tries to find the given file, assuming that it's missing the ".jxp" extension * * @param f File to check * @return same file if not found to be missing the .jxp, or a new File w/ the .jxp appended */ File tryNoJXP(File f) { if (f.exists()) return f; if (f.getName().indexOf(".") >= 0) return f; File temp = new File(f.toString() + ".jxp"); return temp.exists() ? temp : f; } File tryOtherExtensions(File f) { if (f.exists()) return f; if (f.getName().indexOf(".") >= 0) return f; for (int i = 0; i < JSFileLibrary._srcExtensions.length; i++) { File temp = new File(f.toString() + JSFileLibrary._srcExtensions[i]); if (temp.exists()) return temp; } return f; } /** * Maps a servlet-like URI to a jxp file. * * @param f File to check * @return new File with <root>.jxp if exists, orig file if not * @example /wiki/geir -> maps to wiki.jxp if exists */ File tryServlet(File f) { if (f.exists()) return f; String uri = f.toString(); if (uri.startsWith(_rootFile.toString())) uri = uri.substring(_rootFile.toString().length()); if (_core != null && uri.startsWith(_core._base.toString())) uri = "/~~" + uri.substring(_core._base.toString().length()); while (uri.startsWith("/")) uri = uri.substring(1); int start = 0; while (true) { int idx = uri.indexOf("/", start); if (idx < 0) break; String foo = uri.substring(0, idx); File temp = getFileSafe(foo + ".jxp"); if (temp != null && temp.exists()) f = temp; start = idx + 1; } return f; } /** * Returns the index.jxp for the File argument if it's an existing directory, and the index.jxp * file exists * * @param f directory to check * @return new File for index.jxp in that directory, or same file object if not */ File tryIndex(File f) { if (!(f.isDirectory() && f.exists())) return f; for (int i = 0; i < JSFileLibrary._srcExtensions.length; i++) { File temp = new File(f, "index" + JSFileLibrary._srcExtensions[i]); if (temp.exists()) return temp; } return f; } JxpSource getSource(File f) throws IOException { if (DEBUG) System.err.println("getSource\n\t " + f); File temp = _findFile(f); if (DEBUG) System.err.println("\t " + temp); if (!temp.exists()) return handleFileNotFound(f); // if it's a directory (and we know we can't find the index file) // TODO : at some point, do something where we return an index for the dir? if (temp.isDirectory()) return null; // if we are at init time, save it as an initializaiton file loadedFile(temp); // Ensure that this is w/in the right tree for the context if (_localObject != null && _localObject.isIn(temp)) return _localObject.getSource(temp); // if not, is it core? if (_core.isIn(temp)) return _core.getSource(temp); throw new RuntimeException("what? can't find:" + f); } /** * Finds the appropriate file for the given path. * * <p>We have a hierarchy of attempts as we try to find a file : * * <p>1) first, see if it exists as is, or if it's really a .jxp w/o the extension 2) next, see if * it can be deconstructed as a servlet such that /foo/bar maps to /foo.jxp 3) See if we can find * the index file for it if a directory */ File _findFile(File f) { File temp; if ((temp = tryNoJXP(f)) != f) { return temp; } if ((temp = tryOtherExtensions(f)) != f) { return temp; } if ((temp = tryServlet(f)) != f) { return temp; } if ((temp = tryIndex(f)) != f) { return temp; } return f; } public void loadedFile(File f) { if (_inScopeSetup) _initFlies.add(f); } public void addInitDependency(File f) { _initFlies.add(f); } JxpServlet getServlet(File f) throws IOException { // if this site doesn't exist, don't return anything if (!_rootFile.exists()) return null; JxpSource source = getSource(f); if (source == null) return null; return source.getServlet(this); } private void _setupScope() { if (_inScopeSetup) return; final Scope saveTLPref = _scope.getTLPreferred(); _scope.setTLPreferred(null); final Scope saveTL = Scope.getThreadLocal(); _scope.makeThreadLocal(); _inScopeSetup = true; try { Object fo = getConfigObject("framework"); if (fo != null) { Framework f = null; if (fo instanceof JSString) { f = Framework.byName(fo.toString(), null); // we allow people to just specify name _logger.info("Setting framework by name [" + fo.toString() + "]"); } else if (fo instanceof JSObjectBase) { JSObjectBase obj = (JSObjectBase) fo; if (obj.containsKey("name")) { String s = obj.getAsString("version"); f = Framework.byName(obj.getAsString("name"), s); _logger.info("Setting framework by name [" + obj.getAsString("name") + "]"); } else if (obj.containsKey("custom")) { Object o = obj.get("custom"); if (o instanceof JSObjectBase) { f = Framework.byCustom((JSObjectBase) o); _logger.info("Setting framework by custom [" + o + "]"); } else { throw new RuntimeException("Error - custom framework wasn't an object [" + o + "]"); } } else if (obj.containsKey("classname")) { f = Framework.byClass(obj.getAsString("classname")); _logger.info("Setting framework by class [" + obj.getAsString("classname") + "]"); } } if (f == null) { throw new RuntimeException("Error : can't find framework [" + fo + "]"); } f.install(this); } _runInitFiles(INIT_FILES); if (_adminContext != null) { _adminContext._scope.set("siteScope", _scope); _adminContext._setLocalObject(_localObject); } _lastScopeInitTime = _getScopeTime(); } catch (RuntimeException re) { _scopeInited = false; throw re; } catch (Exception e) { _scopeInited = false; throw new RuntimeException(e); } finally { _inScopeSetup = false; _scope.setTLPreferred(saveTLPref); if (saveTL != null) saveTL.makeThreadLocal(); this.approxSize(_rootContextReachable); } } public boolean inScopeSetup() { return _inScopeSetup; } private void _runInitFiles(String[] files) throws IOException { if (files == null) return; for (JSFunction func : _initRefreshHooks) { func.call(_initScope, null); } for (int i = 0; i < files.length; i++) runInitFile(files[i].replaceAll("PREFIX", _codePrefix)); } public void addInitRefreshHook(JSFunction func) { _initRefreshHooks.add(func); } /** @param path (ex: /~~/foo.js ) */ public void runInitFile(String path) throws IOException { _runInitFile(tryOtherExtensions(getFile(path))); } private void _runInitFile(File f) throws IOException { if (f == null) return; if (!f.exists()) return; _initFlies.add(f); JxpSource s = getSource(f); JSFunction func = s.getFunction(); func.setUsePassedInScope(true); func.call(_initScope); } long _getScopeTime() { long last = 0; for (File f : _initFlies) if (f.exists()) last = Math.max(last, f.lastModified()); return last; } /** * Convert this AppContext to a string by returning the name of the directory it's running in. * * @return the filename of its root directory */ public String toString() { return _rootFile.toString(); } public String debugInfo() { return _rootFile + " admin:" + _admin; } public void fix(Throwable t) { StackTraceHolder.getInstance().fix(t); } /** * Get a "global" head array. This array contains HTML that will be inserted into the head of * every request served by this app context. It's analagous to the <tt>head</tt> array, but * persistent. * * @return a mutable array */ public JSArray getGlobalHead() { return _globalHead; } /** * Gets the date of creation for this app context. * * @return the creation date as a JS Date. */ public JSDate getWhenCreated() { return _created; } /** * Gets the number of requests served by this app context. * * @return the number of requests served */ public int getNumRequests() { return _numRequests; } /** * Get the name of the git branch we think we're running. * * @return the name of the git branch, as a string */ public String getGitBranch() { return _gitBranch; } public String getGitHash() { return _gitHash; } /** * Update the git branch that we're running and return it. * * @return the name of the git branch, or null if there isn't any */ public String getCurrentGitBranch() { return getCurrentGitBranch(false); } public String getCurrentGitBranch(boolean forceUpdate) { if (_gitBranch == null) return null; if (_gitFile == null) _gitFile = new File(_rootFile, ".git/HEAD"); if (!_gitFile.exists()) throw new RuntimeException("this should be impossible"); if (forceUpdate || _lastScopeInitTime < _gitFile.lastModified()) { _gitBranch = _git.getBranchOrTagName(); _gitHash = _git.getCurrentHash(); } return _gitBranch; } /** * Get the environment in which this site is running * * @return the environment name as a string */ public String getEnvironmentName() { return _environment; } /** * updates the context to the correct branch based on environment and to the latest version of the * code if name or environemnt is missing, does nothing */ public String updateCode() { if (!_git.isValid()) throw new RuntimeException(_rootFile + " is not a git repository"); _logger.info("going to update code"); _git.fullUpdate(); if (_name == null || _environment == null) return getCurrentGitBranch(); JSObject env = getEnvironmentObject(); if (env == null) return null; String branch = env.get("branch").toString(); _logger.info("updating to [" + branch + "]"); _git.checkout(branch); Python.deleteCachedJythonFiles(_rootFile); return getCurrentGitBranch(true); } private JSObject getSiteObject() { return AppContextHolder.getSiteFromCloud(_name); } private JSObject getEnvironmentObject() { return AppContextHolder.getEnvironmentFromCloud(_name, _environment); } private void _setLocalObject(JSFileLibrary local) { _localObject = local; _scope.put("local", _localObject, true); _scope.put("jxp", _localObject, true); _scope.warn("jxp"); } JxpSource handleFileNotFound(File f) { String name = f.getName(); if (name.endsWith(".class")) { name = name.substring(0, name.length() - 6); return getJxpServlet(name); } return null; } public JxpSource getJxpServlet(String name) { JxpSource source = _httpServlets.get(name); if (source != null) return source; try { Class c = Class.forName(name); Object n = c.newInstance(); if (!(n instanceof HttpServlet)) throw new RuntimeException("class [" + name + "] is not a HttpServlet"); HttpServlet servlet = (HttpServlet) n; servlet.init(createServletConfig(name)); source = new ServletSource(servlet); _httpServlets.put(name, source); return source; } catch (Exception e) { throw new RuntimeException("can't load [" + name + "]", e); } } ServletConfig createServletConfig(final String name) { final Object rawServletConfigs = _scope.get("servletConfigs"); final Object servletConfigObject = rawServletConfigs instanceof JSObject ? ((JSObject) rawServletConfigs).get(name) : null; final JSObject servletConfig; if (servletConfigObject instanceof JSObject) servletConfig = (JSObject) servletConfigObject; else servletConfig = null; return new ServletConfig() { public String getInitParameter(String name) { if (servletConfig == null) return null; Object foo = servletConfig.get(name); if (foo == null) return null; return foo.toString(); } public Enumeration getInitParameterNames() { Collection keys; if (servletConfig == null) keys = new LinkedList(); else keys = servletConfig.keySet(); return new CollectionEnumeration(keys); } public ServletContext getServletContext() { return AppContext.this; } public String getServletName() { return name; } }; } public static AppContext findThreadLocal() { AppContext context = _tl.get(); if (context != null) return context; AppRequest req = AppRequest.getThreadLocal(); if (req != null) return req._context; Scope s = Scope.getThreadLocal(); if (s != null) { Object foo = s.get("__instance__"); if (foo instanceof AppContext) return (AppContext) foo; } return null; } public void makeThreadLocal() { _tl.set(this); } public static void clearThreadLocal() { _tl.set(null); } public String getInitParameter(String name) { Object foo = _configScope.get(name); if (foo == null) return null; return foo.toString(); } public Enumeration getInitParameterNames() { return new CollectionEnumeration(_configScope.keySet()); } public Object getConfigObject(String name) { return _configScope.get(name); } public void setConfigObject(String name, Object value) { _configScope.set(name, value); } public String getContextPath() { return ""; } public RequestDispatcher getNamedDispatcher(String name) { throw new RuntimeException("getNamedDispatcher not implemented"); } public RequestDispatcher getRequestDispatcher(String name) { throw new RuntimeException("getRequestDispatcher not implemented"); } public Set getResourcePaths(String path) { throw new RuntimeException("getResourcePaths not implemented"); } public AppContext getSiteInstance() { if (_nonAdminParent == null) return this; return _nonAdminParent; } public long approxSize() { return approxSize(new SeenPath()); } public long approxSize(SeenPath seen) { long size = 0; seen.visited(this); if (seen.shouldVisit(_scope, this)) size += _scope.approxSize(seen, false, true); if (seen.shouldVisit(_initScope, this)) size += _initScope.approxSize(seen, true, false); size += JSObjectSize.size(_localObject, seen, this); size += JSObjectSize.size(_core, seen, this); size += JSObjectSize.size(_external, seen, this); if (seen.shouldVisit(_adminContext, this)) size += _adminContext.approxSize(seen); size += JSObjectSize.size(_logger, seen, this); return size; } public int hashCode() { return System.identityHashCode(this); } public boolean equals(Object o) { return o == this; } public AppWork queueWork(String identifier, JSFunction work, Object... params) { return queueWork(new AppWork.FunctionAppWork(this, identifier, work, params)); } public AppWork queueWork(AppWork work) { if (_workQueue == null) { _workQueue = new ArrayBlockingQueue<AppWork>(100); AppWork.addQueue(_workQueue); } if (_workQueue.offer(work)) return work; throw new RuntimeException("work queue full!"); } public Logger getLogger(String sub) { return _logger.getChild(sub); } public ModuleRegistry getModuleRegistry() { return _moduleRegistry; } // ---- START JSObject INTERFACE public Object get(Object n) { return _scope.get(n); } public JSFunction getFunction(String name) { return _scope.getFunction(name); } public final Set<String> keySet() { return _scope.keySet(); } public Set<String> keySet(boolean includePrototype) { return _scope.keySet(includePrototype); } public boolean containsKey(String s) { return _scope.containsKey(s); } public boolean containsKey(String s, boolean includePrototype) { return _scope.containsKey(s, includePrototype); } public Object set(Object n, Object v) { return _scope.putExplicit(n.toString(), v); } public Object setInt(int n, Object v) { throw new RuntimeException("not allowed"); } public Object getInt(int n) { return _scope.getInt(n); } public Object removeField(Object n) { return _scope.removeField(n); } public JSFunction getConstructor() { return null; } public JSObject getSuper() { return null; } // ---- END BROKEN JSOBJET INTERFACE public TimeZone getTimeZone() { return _tz; } public void setTimeZone(String tz) { if (tz.length() == 3) tz = tz.toUpperCase(); _tz = TimeZone.getTimeZone(tz); if (!_tz.getID().equals(tz)) throw new RuntimeException("can't find time zone[" + tz + "]"); } final String _name; final String _root; final File _rootFile; final GitDir _git; private String _gitBranch; private String _gitHash; final String _environment; final boolean _admin; final AppContext _adminContext; final String _codePrefix; final AppContext _nonAdminParent; private JSFileLibrary _localObject; private JSFileLibrary _core; private JSFileLibrary _external; final Scope _scope; final SeenPath _rootContextReachable; final Scope _initScope; final Scope _configScope = new Scope(); final UsageTracker _usage; final ModuleRegistry _moduleRegistry; final JSArray _globalHead = new JSArray(); private final Map<String, File> _files = Collections.synchronizedMap(new HashMap<String, File>()); private final Set<File> _initFlies = new HashSet<File>(); private final Map<String, JxpSource> _httpServlets = Collections.synchronizedMap(new HashMap<String, JxpSource>()); private final JSObject _libraryVersions = new JSObjectBase(); private Queue<AppWork> _workQueue; private TimeZone _tz = TimeZone.getDefault(); boolean _scopeInited = false; boolean _inScopeSetup = false; long _lastScopeInitTime = 0; final boolean _isGrid; boolean _reset = false; int _numRequests = 0; final JSDate _created = new JSDate(); private File _gitFile = null; private long _lastGitCheckTime = 0; private Collection<JSFunction> _initRefreshHooks = new ArrayList<JSFunction>(); /* * adapter type - can have either a static ("all files in this app are X") * or dynamic - the provided selector function dynamically chooses, falling * back to the static if it returns null */ public static final String INIT_ADAPTER_TYPE = "adapterType"; public static final String INIT_ADAPTER_SELECTOR = "adapterSelector"; private AdapterType _staticAdapterType = AdapterType.UNSET; private JSFunction _adapterSelector = null; private static Logger _libraryLogger = Logger.getLogger("library.load"); static { _libraryLogger.setLevel(Level.INFO); } private static final Set<String> _knownInitScopeThings = new HashSet<String>(); private static final ThreadLocal<AppContext> _tl = new ThreadLocal<AppContext>(); static { _knownInitScopeThings.add("mapUrlToJxpFileCore"); _knownInitScopeThings.add("mapUrlToJxpFile"); _knownInitScopeThings.add("allowed"); _knownInitScopeThings.add("staticCacheTime"); _knownInitScopeThings.add("handle404"); _knownInitScopeThings.add(INIT_ADAPTER_TYPE); _knownInitScopeThings.add(INIT_ADAPTER_SELECTOR); } public static final class AppContextReachable extends ReflectionVisitor.Reachable { public boolean follow(Object o, Class c, java.lang.reflect.Field f) { if (_reachableStoppers.contains(c)) return false; if (f != null && _reachableStoppers.contains(f.getType())) return false; return super.follow(o, c, f); } } private static final Set<Class> _reachableStoppers = new HashSet<Class>(); static { _reachableStoppers.add(HttpServer.class); _reachableStoppers.add(AppServer.class); _reachableStoppers.add(DBTCP.class); _reachableStoppers.add(Mongo.class); _reachableStoppers.add(WeakBag.class); _reachableStoppers.add(WeakValueMap.class); } }
public void setTimeZone(String tz) { if (tz.length() == 3) tz = tz.toUpperCase(); _tz = TimeZone.getTimeZone(tz); if (!_tz.getID().equals(tz)) throw new RuntimeException("can't find time zone[" + tz + "]"); }
public class NetUtils { private static Logger logger = LoggerFactory.createLogger(NetUtils.class); public static final String DYNAMIC_RESOURCES_SESSION_KEY = "orbeon.resources.dynamic."; // Resources are served by the XForms server. It is not ideal to refer to XForms-related // functionality from here. public static final String DYNAMIC_RESOURCES_PATH = "/xforms-server/dynamic/"; private static final Pattern PATTERN_NO_AMP; private static final Pattern PATTERN_AMP; // private static final Pattern PATTERN_AMP_AMP; public static final String STANDARD_PARAMETER_ENCODING = "utf-8"; private static final SimpleDateFormat dateHeaderFormats[] = { new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US), new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) }; private static final TimeZone gmtZone = TimeZone.getTimeZone("GMT"); private static FileItemFactory fileItemFactory; public static final int REQUEST_SCOPE = 0; public static final int SESSION_SCOPE = 1; public static final int APPLICATION_SCOPE = 2; // Default HTTP 1.1 charset for text/* mediatype public static final String DEFAULT_HTTP_TEXT_READING_ENCODING = "iso-8859-1"; // Default RFC 3023 default charset for txt/xml mediatype public static final String DEFAULT_TEXT_XML_READING_ENCODING = "us-ascii"; public static final String APPLICATION_SOAP_XML = "application/soap+xml"; static { // Set timezone to GMT as required for HTTP headers for (SimpleDateFormat dateHeaderFormat : dateHeaderFormats) dateHeaderFormat.setTimeZone(gmtZone); final String notEqNorAmpChar = "[^=&]"; final String token = notEqNorAmpChar + "+"; PATTERN_NO_AMP = Pattern.compile("(" + token + ")=(" + token + ")(?:&|(?<!&)\\z)"); PATTERN_AMP = Pattern.compile("(" + token + ")=(" + token + ")(?:&|&|(?<!&|&)\\z)"); // PATTERN_AMP_AMP = Pattern.compile( "(" + token + ")=(" + token + // ")(?:&amp;|&|(?<!&amp;|&)\\z)" ); } public static long getDateHeader(String stringValue) throws ParseException { for (SimpleDateFormat dateHeaderFormat : dateHeaderFormats) { try { Date date = dateHeaderFormat.parse(stringValue); return date.getTime(); } catch ( Exception e) { // used to be ParseException, but NumberFormatException may be thrown as well // Ignore and try next } } throw new ParseException(stringValue, 0); } /** * Return true if the document was modified since the given date, based on the If-Modified-Since * header. If the request method was not "GET", or if no valid lastModified value was provided, * consider the document modified. */ public static boolean checkIfModifiedSince(HttpServletRequest request, long lastModified) { // Do the check only for the GET method if (!"GET".equals(request.getMethod()) || lastModified <= 0) return true; // Check dates String ifModifiedHeader = request.getHeader("If-Modified-Since"); if (logger.isDebugEnabled()) logger.debug("Found If-Modified-Since header"); if (ifModifiedHeader != null) { try { long dateTime = getDateHeader(ifModifiedHeader); if (lastModified <= (dateTime + 1000)) { if (logger.isDebugEnabled()) logger.debug("Sending SC_NOT_MODIFIED response"); return false; } } catch ( Exception e) { // used to be ParseException, but NumberFormatException may be thrown as well // Ignore } } return true; } /** * Return a request path info that looks like what one would expect. The path starts with a "/", * relative to the servlet context. If the servlet was included or forwarded to, return the path * by which the *current* servlet was invoked, NOT the path of the calling servlet. * * <p>Request path = servlet path + path info. * * @param request servlet HTTP request * @return path */ public static String getRequestPathInfo(HttpServletRequest request) { // NOTE: Servlet 2.4 spec says: "These attributes [javax.servlet.include.*] are accessible from // the included // servlet via the getAttribute method on the request object and their values must be equal to // the request URI, // context path, servlet path, path info, and query string of the included servlet, // respectively." // NOTE: This is very different from the similarly-named forward attributes! // Get servlet path String servletPath = (String) request.getAttribute("javax.servlet.include.servlet_path"); if (servletPath == null) { servletPath = request.getServletPath(); if (servletPath == null) servletPath = ""; } // Get path info String pathInfo = (String) request.getAttribute("javax.servlet.include.path_info"); if (pathInfo == null) { pathInfo = request.getPathInfo(); if (pathInfo == null) pathInfo = ""; } // Concatenate servlet path and path info, avoiding a double slash String requestPath = servletPath.endsWith("/") && pathInfo.startsWith("/") ? servletPath + pathInfo.substring(1) : servletPath + pathInfo; // Add starting slash if missing if (!requestPath.startsWith("/")) requestPath = "/" + requestPath; return requestPath; } /** * Return the last modification date of the given absolute URL if it is "fast" to do so, i.e. if * it is an "oxf:" or a "file:" protocol. * * @param absoluteURL absolute URL to check * @return last modification date if "fast" or 0 if not fast or if an error occurred */ public static long getLastModifiedIfFast(String absoluteURL) { final long lastModified; if (absoluteURL.startsWith("oxf:") || absoluteURL.startsWith("file:")) { try { lastModified = getLastModified(URLFactory.createURL(absoluteURL)); } catch (IOException e) { throw new OXFException(e); } } else { // Value of 0 for lastModified will cause XFormsResourceServer to set Last-Modified and // Expires properly to "now". lastModified = 0; } return lastModified; } /** * Get the last modification date of a URL. * * @return last modified timestamp, null if le 0 */ public static Long getLastModifiedAsLong(URL url) throws IOException { final long connectionLastModified = getLastModified(url); // Zero and negative values often have a special meaning, make sure to normalize here return connectionLastModified <= 0 ? null : connectionLastModified; } /** * Get the last modification date of a URL. * * @return last modified timestamp "as is" */ public static long getLastModified(URL url) throws IOException { if ("file".equals(url.getProtocol())) { // Optimize file: access. Also, this prevents throwing an exception if the file doesn't exist // as we try to close the stream below. return new File(URLDecoder.decode(url.getFile(), STANDARD_PARAMETER_ENCODING)).lastModified(); } else { // Use URLConnection final URLConnection urlConnection = url.openConnection(); if (urlConnection instanceof HttpURLConnection) ((HttpURLConnection) urlConnection).setRequestMethod("HEAD"); try { return getLastModified(urlConnection); } finally { final InputStream is = urlConnection.getInputStream(); if (is != null) is.close(); } } } /** * Get the last modification date of an open URLConnection. * * <p>This handles the (broken at some point in the Java libraries) case of the file: protocol. * * @return last modified timestamp, null if le 0 */ public static Long getLastModifiedAsLong(URLConnection urlConnection) { final long connectionLastModified = getLastModified(urlConnection); // Zero and negative values often have a special meaning, make sure to normalize here return connectionLastModified <= 0 ? null : connectionLastModified; } /** * Get the last modification date of an open URLConnection. * * <p>This handles the (broken at some point in the Java libraries) case of the file: protocol. * * @return last modified timestamp "as is" */ public static long getLastModified(URLConnection urlConnection) { try { long lastModified = urlConnection.getLastModified(); if (lastModified == 0 && "file".equals(urlConnection.getURL().getProtocol())) lastModified = new File( URLDecoder.decode( urlConnection.getURL().getFile(), STANDARD_PARAMETER_ENCODING)) .lastModified(); return lastModified; } catch (UnsupportedEncodingException e) { // Should not happen as we are using a required encoding throw new OXFException(e); } } /** Check if an URL is relative to another URL. */ public static boolean relativeURL(URL url1, URL url2) { return ((url1.getProtocol() == null && url2.getProtocol() == null) || url1.getProtocol().equals(url2.getProtocol())) && ((url1.getAuthority() == null && url2.getAuthority() == null) || url1.getAuthority().equals(url2.getAuthority())) && ((url1.getPath() == null && url2.getPath() == null) || url2.getPath().startsWith(url1.getPath())); } public static void copyStream(InputStream is, OutputStream os) throws IOException { int count; byte[] buffer = new byte[1024]; while ((count = is.read(buffer)) > 0) os.write(buffer, 0, count); } public static void copyStream(Reader reader, Writer writer) throws IOException { int count; char[] buffer = new char[1024]; while ((count = reader.read(buffer)) > 0) writer.write(buffer, 0, count); } public static String readStreamAsString(Reader reader) throws IOException { final StringBuilderWriter writer = new StringBuilderWriter(); copyStream(reader, writer); return writer.toString(); } public static String getContentTypeCharset(String contentType) { final Map<String, String> parameters = getContentTypeParameters(contentType); return (parameters == null) ? null : parameters.get("charset"); } public static Map<String, String> getContentTypeParameters(String contentType) { if (contentType == null) return null; // Check whether there may be parameters final int semicolonIndex = contentType.indexOf(";"); if (semicolonIndex == -1) return null; // Tokenize final StringTokenizer st = new StringTokenizer(contentType, ";"); if (!st.hasMoreTokens()) return null; // should not happen as there should be at least the content type st.nextToken(); // No parameters if (!st.hasMoreTokens()) return null; // Parse parameters final Map<String, String> parameters = new HashMap<String, String>(); while (st.hasMoreTokens()) { final String parameter = st.nextToken().trim(); final int equalIndex = parameter.indexOf('='); if (equalIndex == -1) continue; final String name = parameter.substring(0, equalIndex).trim(); final String value = parameter.substring(equalIndex + 1).trim(); parameters.put(name, value); } return parameters; } public static Map<String, String> getCharsetHeaderCharsets(String header) { if (header == null) return null; int semicolonIndex = header.indexOf(";"); final String charsets; if (semicolonIndex == -1) charsets = header.trim(); else charsets = header.substring(0, semicolonIndex).trim(); final StringTokenizer st = new StringTokenizer(charsets, ","); final Map<String, String> charsetsMap = new HashMap<String, String>(); while (st.hasMoreTokens()) { charsetsMap.put(st.nextToken(), ""); } return charsetsMap; } public static String getContentTypeMediaType(String contentType) { if (contentType == null || contentType.equalsIgnoreCase("content/unknown")) return null; int semicolonIndex = contentType.indexOf(";"); if (semicolonIndex == -1) return contentType; return contentType.substring(0, semicolonIndex).trim(); } /** * @param queryString a query string of the form n1=v1&n2=v2&... to decode. May be null. * @param acceptAmp -> "&" if true, "&" if false * @return a Map of String[] indexed by name, an empty Map if the query string was null */ public static Map<String, String[]> decodeQueryString( final CharSequence queryString, final boolean acceptAmp) { final Map<String, String[]> result = new TreeMap<String, String[]>(); if (queryString != null) { final Matcher matcher = acceptAmp ? PATTERN_AMP.matcher(queryString) : PATTERN_NO_AMP.matcher(queryString); int matcherEnd = 0; while (matcher.find()) { matcherEnd = matcher.end(); try { // Group 0 is the whole match, e.g. a=b, while group 1 is the first group // denoted ( with parens ) in the expression. Hence we start with group 1. final String name = URLDecoder.decode(matcher.group(1), NetUtils.STANDARD_PARAMETER_ENCODING); final String value = URLDecoder.decode(matcher.group(2), NetUtils.STANDARD_PARAMETER_ENCODING); StringUtils.addValueToStringArrayMap(result, name, value); } catch (UnsupportedEncodingException e) { // Should not happen as we are using a required encoding throw new OXFException(e); } } if (queryString.length() != matcherEnd) { // There was garbage at the end of the query. throw new OXFException("Malformed URL: " + queryString); } } return result; } /** Encode a query string. The input Map contains names indexing Object[]. */ public static String encodeQueryString(Map parameters) { final StringBuilder sb = new StringBuilder(100); boolean first = true; try { for (Object o : parameters.keySet()) { final String name = (String) o; final Object[] values = (Object[]) parameters.get(name); for (final Object currentValue : values) { if (currentValue instanceof String) { if (!first) sb.append('&'); sb.append(URLEncoder.encode(name, NetUtils.STANDARD_PARAMETER_ENCODING)); sb.append('='); sb.append( URLEncoder.encode((String) currentValue, NetUtils.STANDARD_PARAMETER_ENCODING)); first = false; } } } } catch (UnsupportedEncodingException e) { // Should not happen as we are using a required encoding throw new OXFException(e); } return sb.toString(); } /** Combine a path info and a parameters map to form a path info with a query string. */ public static String pathInfoParametersToPathInfoQueryString(String pathInfo, Map parameters) throws IOException { final StringBuilder redirectURL = new StringBuilder(pathInfo); if (parameters != null) { boolean first = true; for (Object o : parameters.keySet()) { final String name = (String) o; final Object[] values = (Object[]) parameters.get(name); for (final Object currentValue : values) { if (currentValue instanceof String) { redirectURL.append(first ? "?" : "&"); redirectURL.append(URLEncoder.encode(name, NetUtils.STANDARD_PARAMETER_ENCODING)); redirectURL.append("="); redirectURL.append( URLEncoder.encode((String) currentValue, NetUtils.STANDARD_PARAMETER_ENCODING)); first = false; } } } } return redirectURL.toString(); } /** * Append a query string to an URL. This adds a '?' or a '&' or nothing, as needed. * * @param urlString existing URL string * @param queryString query string, or null * @return resulting URL */ public static String appendQueryString(String urlString, String queryString) { if (org.apache.commons.lang.StringUtils.isBlank(queryString)) { return urlString; } else { final StringBuilder updatedActionStringBuilder = new StringBuilder(urlString); updatedActionStringBuilder.append((urlString.indexOf('?') == -1) ? '?' : '&'); updatedActionStringBuilder.append(queryString); return updatedActionStringBuilder.toString(); } } /** * Check whether a URL starts with a protocol. * * <p>We consider that a protocol consists only of ASCII letters and must be at least two * characters long, to avoid confusion with Windows drive letters. */ public static boolean urlHasProtocol(String urlString) { int colonIndex = urlString.indexOf(":"); // No protocol is there is no colon or if there is only one character in the protocol if (colonIndex == -1 || colonIndex == 1) return false; // Check that there is a protocol boolean allChar = true; for (int i = 0; i < colonIndex; i++) { char c = urlString.charAt(i); if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z')) { allChar = false; break; } } return allChar; } /** * Resolve a URI against a base URI. (Be sure to pay attention to the order or parameters.) * * @param href URI to resolve (accept human-readable URI) * @param base URI base (accept human-readable URI) * @return resolved URI */ public static String resolveURI(String href, String base) { final String resolvedURIString; if (base != null) { final URI baseURI; try { baseURI = new URI(encodeHRRI(base, true)); } catch (URISyntaxException e) { throw new OXFException(e); } resolvedURIString = baseURI .resolve(encodeHRRI(href, true)) .normalize() .toString(); // normalize to remove "..", etc. } else { resolvedURIString = encodeHRRI(href, true); } return resolvedURIString; } public static String headersToString(HttpServletRequest request) { final StringBuffer sb = new StringBuffer(); for (Enumeration e = request.getHeaderNames(); e.hasMoreElements(); ) { final String name = (String) e.nextElement(); sb.append(name); sb.append("="); for (Enumeration f = request.getHeaders(name); f.hasMoreElements(); ) { final String value = (String) f.nextElement(); sb.append(value); if (f.hasMoreElements()) sb.append(","); } if (e.hasMoreElements()) sb.append("|"); } return sb.toString(); } public static String readURIToLocalURI(String uri) throws URISyntaxException, IOException { final PipelineContext pipelineContext = StaticExternalContext.getStaticContext().getPipelineContext(); final URLConnection urlConnection = new URI(uri).toURL().openConnection(); InputStream inputStream = null; try { inputStream = urlConnection.getInputStream(); return inputStreamToAnyURI(pipelineContext, inputStream, REQUEST_SCOPE); } finally { if (inputStream != null) inputStream.close(); } } public static byte[] base64StringToByteArray(String base64String) { return Base64.decode(base64String); } /** * Convert a String in xs:base64Binary to an xs:anyURI. * * <p>NOTE: The implementation creates a temporary file. The Pipeline Context is required so that * the file can be deleted when no longer used. */ public static String base64BinaryToAnyURI( PipelineContext pipelineContext, String value, int scope) { // Convert Base64 to binary first final byte[] bytes = base64StringToByteArray(value); return inputStreamToAnyURI(pipelineContext, new ByteArrayInputStream(bytes), scope); } /** * Read an InputStream into a byte array. * * @param is InputStream * @return byte array */ public static byte[] inputStreamToByteArray(InputStream is) { try { final ByteArrayOutputStream os = new ByteArrayOutputStream(); copyStream(new BufferedInputStream(is), os); os.close(); return os.toByteArray(); } catch (Exception e) { throw new OXFException(e); } } /** * Read a URI into a byte array. * * @param uri URI to read * @return byte array */ public static byte[] uriToByteArray(String uri) { InputStream is = null; try { is = new URI(uri).toURL().openStream(); return inputStreamToByteArray(is); } catch (Exception e) { throw new OXFException(e); } finally { try { if (is != null) is.close(); } catch (IOException e) { throw new OXFException(e); } } } /** * Convert a URI to a FileItem. * * <p>The implementation creates a temporary file. The PipelineContext is required so that the * file can be deleted when no longer used. */ public static FileItem anyURIToFileItem(PipelineContext pipelineContext, String uri, int scope) { InputStream inputStream = null; try { inputStream = new URI(uri).toURL().openStream(); // Get FileItem return prepareFileItemFromInputStream(pipelineContext, inputStream, scope); } catch (Exception e) { throw new OXFException(e); } finally { try { if (inputStream != null) inputStream.close(); } catch (IOException e) { throw new OXFException(e); } } } /** * Convert an InputStream to an xs:anyURI. * * <p>The implementation creates a temporary file. The PipelineContext is required so that the * file can be deleted when no longer used. */ public static String inputStreamToAnyURI( PipelineContext pipelineContext, InputStream inputStream, int scope) { // Get FileItem final FileItem fileItem = prepareFileItemFromInputStream(pipelineContext, inputStream, scope); // Return a file URL final File storeLocation = ((DiskFileItem) fileItem).getStoreLocation(); return storeLocation.toURI().toString(); } private static FileItem prepareFileItemFromInputStream( PipelineContext pipelineContext, InputStream inputStream, int scope) { // Get FileItem final FileItem fileItem = prepareFileItem(pipelineContext, scope); // Write to file OutputStream os = null; try { os = fileItem.getOutputStream(); copyStream(inputStream, os); } catch (IOException e) { throw new OXFException(e); } finally { if (os != null) { try { os.close(); } catch (IOException e) { throw new OXFException(e); } } } // Create file if it doesn't exist (necessary when the file size is 0) final File storeLocation = ((DiskFileItem) fileItem).getStoreLocation(); try { storeLocation.createNewFile(); } catch (IOException e) { throw new OXFException(e); } return fileItem; } /** * Return a FileItem which is going to be automatically destroyed upon destruction of the request, * session or application. */ public static FileItem prepareFileItem(PipelineContext pipelineContext, int scope) { // We use the commons file upload utilities to save a file if (fileItemFactory == null) fileItemFactory = new DiskFileItemFactory(0, SystemUtils.getTemporaryDirectory()); final FileItem fileItem = fileItemFactory.createItem("dummy", "dummy", false, null); // Make sure the file is deleted appropriately if (scope == REQUEST_SCOPE) { deleteFileOnRequestEnd(pipelineContext, fileItem); } else if (scope == SESSION_SCOPE) { deleteFileOnSessionTermination(pipelineContext, fileItem); } else if (scope == APPLICATION_SCOPE) { deleteFileOnContextDestroyed(pipelineContext, fileItem); } else { throw new OXFException("Invalid context requested: " + scope); } // Return FileItem object return fileItem; } /** * Add listener to fileItem which is going to be automatically destroyed at the end of request * * @param pipelineContext PipelineContext * @param fileItem FileItem */ public static void deleteFileOnRequestEnd( PipelineContext pipelineContext, final FileItem fileItem) { // Make sure the file is deleted at the end of request pipelineContext.addContextListener( new PipelineContext.ContextListenerAdapter() { public void contextDestroyed(boolean success) { deleteFileItem(fileItem, REQUEST_SCOPE); } }); } /** * Add listener to fileItem which is going to be automatically destroyed on session destruction * * @param pipelineContext PipelineContext * @param fileItem FileItem */ public static void deleteFileOnSessionTermination( PipelineContext pipelineContext, final FileItem fileItem) { // Try to delete the file on exit and on session termination final ExternalContext externalContext = (ExternalContext) pipelineContext.getAttribute(PipelineContext.EXTERNAL_CONTEXT); final ExternalContext.Session session = externalContext.getSession(false); if (session != null) { session.addListener( new ExternalContext.Session.SessionListener() { public void sessionDestroyed() { deleteFileItem(fileItem, SESSION_SCOPE); } }); } else { logger.debug( "No existing session found so cannot register temporary file deletion upon session destruction: " + fileItem.getName()); } } /** * Add listener to fileItem which is going to be automatically destroyed when the servlet is * destroyed * * @param pipelineContext PipelineContext * @param fileItem FileItem */ public static void deleteFileOnContextDestroyed( PipelineContext pipelineContext, final FileItem fileItem) { // Try to delete the file on exit and on session termination final ExternalContext externalContext = (ExternalContext) pipelineContext.getAttribute(PipelineContext.EXTERNAL_CONTEXT); ExternalContext.Application application = externalContext.getApplication(); if (application != null) { application.addListener( new ExternalContext.Application.ApplicationListener() { public void servletDestroyed() { deleteFileItem(fileItem, APPLICATION_SCOPE); } }); } else { logger.debug( "No application object found so cannot register temporary file deletion upon session destruction: " + fileItem.getName()); } } private static void deleteFileItem(FileItem fileItem, int scope) { if (logger.isDebugEnabled() && fileItem instanceof DiskFileItem) { final File storeLocation = ((DiskFileItem) fileItem).getStoreLocation(); if (storeLocation != null) { final String temporaryFileName = storeLocation.getAbsolutePath(); final String scopeString = (scope == REQUEST_SCOPE) ? "request" : (scope == SESSION_SCOPE) ? "session" : "application"; logger.debug("Deleting temporary " + scopeString + "-scoped file: " + temporaryFileName); } } fileItem.delete(); } /** * Convert a String in xs:anyURI to an xs:base64Binary. * * <p>The URI has to be a URL. It is read entirely */ public static String anyURIToBase64Binary(String value) { InputStream is = null; try { // Read from URL and convert to Base64 is = URLFactory.createURL(value).openStream(); final StringBuffer sb = new StringBuffer(); XMLUtils.inputStreamToBase64Characters( is, new ContentHandlerAdapter() { public void characters(char ch[], int start, int length) { sb.append(ch, start, length); } }); // Return Base64 String return sb.toString(); } catch (IOException e) { throw new OXFException(e); } finally { if (is != null) { try { is.close(); } catch (IOException e) { throw new OXFException(e); } } } } public static void anyURIToOutputStream(String value, OutputStream outputStream) { InputStream is = null; try { is = URLFactory.createURL(value).openStream(); copyStream(is, outputStream); } catch (IOException e) { throw new OXFException(e); } finally { if (is != null) { try { is.close(); } catch (IOException e) { throw new OXFException(e); } } } } /** * Return the charset associated with a text/* Content-Type header. If a charset is present, * return it. Otherwise, guess depending on whether the mediatype is text/xml or not. * * @param contentType Content-Type header value * @return charset */ public static String getTextCharsetFromContentType(String contentType) { final String charset; final String connectionCharset = getContentTypeCharset(contentType); if (connectionCharset != null) { charset = connectionCharset; } else { // RFC 3023: "Conformant with [RFC2046], if a text/xml entity is // received with the charset parameter omitted, MIME processors and // XML processors MUST use the default charset value of // "us-ascii"[ASCII]. In cases where the XML MIME entity is // transmitted via HTTP, the default charset value is still // "us-ascii". (Note: There is an inconsistency between this // specification and HTTP/1.1, which uses ISO-8859-1[ISO8859] as the // default for a historical reason. Since XML is a new format, a new // default should be chosen for better I18N. US-ASCII was chosen, // since it is the intersection of UTF-8 and ISO-8859-1 and since it // is already used by MIME.)" if (XMLUtils.isXMLMediatype(contentType)) charset = DEFAULT_TEXT_XML_READING_ENCODING; else charset = DEFAULT_HTTP_TEXT_READING_ENCODING; } return charset; } /** * Remove the first path element of a path. Return null if there is only one path element * * <p>E.g. /foo/bar => /bar?a=b * * @param path path to modify * @return modified path or null */ public static String removeFirstPathElement(String path) { final int secondSlashIndex = path.indexOf('/', 1); if (secondSlashIndex == -1) return null; return path.substring(secondSlashIndex); } /** * Return the first path element of a path. If there is only one path element, return the entire * path. * * <p>E.g. /foo/bar => /foo * * @param path path to analyze * @return first path element */ public static String getFirstPathElement(String path) { final int secondSlashIndex = path.indexOf('/', 1); if (secondSlashIndex == -1) return path; return path.substring(0, secondSlashIndex); } /** * Transform an URI accessible from the server into a URI accessible from the client. The mapping * expires with the session. * * @param propertyContext context to obtain session * @param uri server URI to transform * @param filename file name * @param contentType type of the content referred to by the URI, or null if unknown * @param lastModified last modification timestamp * @return client URI */ public static String proxyURI( PropertyContext propertyContext, String uri, String filename, String contentType, long lastModified) { // Create a digest, so that for a given URI we always get the same key final String digest = SecureUtils.digestString(uri, "MD5", "hex"); // Get session final ExternalContext externalContext = (ExternalContext) propertyContext.getAttribute(PipelineContext.EXTERNAL_CONTEXT); final ExternalContext.Session session = externalContext.getSession( true); // NOTE: We force session creation here. Should we? What's the alternative? if (session != null) { // Store mapping into session session .getAttributesMap(ExternalContext.Session.APPLICATION_SCOPE) .put( DYNAMIC_RESOURCES_SESSION_KEY + digest, new DynamicResource(uri, filename, contentType, -1, lastModified)); } // Rewrite new URI to absolute path without the context return DYNAMIC_RESOURCES_PATH + digest; } /** * Utility method to decode a multipart/fomr-data stream and return a Map of parameters of type * Object[], each of which can be a String or FileData. */ public static Map<String, Object[]> getParameterMapMultipart( PipelineContext pipelineContext, final ExternalContext.Request request, String headerEncoding) { final Map<String, Object[]> uploadParameterMap = new HashMap<String, Object[]>(); try { // Setup commons upload // Read properties // NOTE: We use properties scoped in the Request generator for historical reasons. Not too // good. int maxSize = RequestGenerator.getMaxSizeProperty(); int maxMemorySize = RequestGenerator.getMaxMemorySizeProperty(); final DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory(maxMemorySize, SystemUtils.getTemporaryDirectory()); final ServletFileUpload upload = new ServletFileUpload(diskFileItemFactory) { protected FileItem createItem(Map headers, boolean isFormField) throws FileUploadException { if (isFormField) { // Handle externalized values final String externalizeFormValuesPrefix = org.orbeon.oxf.properties.Properties.instance() .getPropertySet() .getString(ServletExternalContext.EXTERNALIZE_FORM_VALUES_PREFIX_PROPERTY); final String fieldName = getFieldName(headers); if (externalizeFormValuesPrefix != null && fieldName.startsWith(externalizeFormValuesPrefix)) { // In this case, we do as if the value content is an uploaded file so that it can // be externalized return super.createItem(headers, false); } else { // Just create the FileItem using the default way return super.createItem(headers, isFormField); } } else { // Just create the FileItem using the default way return super.createItem(headers, isFormField); } } }; upload.setHeaderEncoding(headerEncoding); upload.setSizeMax(maxSize); // Add a listener to destroy file items when the pipeline context is destroyed pipelineContext.addContextListener( new PipelineContext.ContextListenerAdapter() { public void contextDestroyed(boolean success) { for (final String name : uploadParameterMap.keySet()) { final Object values[] = uploadParameterMap.get(name); for (final Object currentValue : values) { if (currentValue instanceof FileItem) { final FileItem fileItem = (FileItem) currentValue; fileItem.delete(); } } } } }); // Wrap and implement just the required methods for the upload code final InputStream inputStream; try { inputStream = request.getInputStream(); } catch (IOException e) { throw new OXFException(e); } final RequestContext requestContext = new RequestContext() { public int getContentLength() { return request.getContentLength(); } public InputStream getInputStream() { // NOTE: The upload code does not actually check that it doesn't read more than the // content-length // sent by the client! Maybe here would be a good place to put an interceptor and make // sure we // don't read too much. return new InputStream() { public int read() throws IOException { return inputStream.read(); } }; } public String getContentType() { return request.getContentType(); } public String getCharacterEncoding() { return request.getCharacterEncoding(); } }; // Parse the request and add file information try { for (Object o : upload.parseRequest(requestContext)) { final FileItem fileItem = (FileItem) o; // Add value to existing values if any if (fileItem.isFormField()) { // Simple form field // Assume that form fields are in UTF-8. Can they have another encoding? If so, how is // it specified? StringUtils.addValueToObjectArrayMap( uploadParameterMap, fileItem.getFieldName(), fileItem.getString(STANDARD_PARAMETER_ENCODING)); } else { // File StringUtils.addValueToObjectArrayMap( uploadParameterMap, fileItem.getFieldName(), fileItem); } } } catch (FileUploadBase.SizeLimitExceededException e) { // Should we do something smart so we can use the Presentation // Server error page anyway? Right now, this is going to fail // miserably with an error. throw e; } catch (UnsupportedEncodingException e) { // Should not happen throw new OXFException(e); } finally { // Close the input stream; if we don't nobody does, and if this stream is // associated with a temporary file, that file may resist deletion if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { throw new OXFException(e); } } } return uploadParameterMap; } catch (FileUploadException e) { throw new OXFException(e); } } /** * Encode a Human Readable Resource Identifier to a URI. Leading and trailing spaces are removed * first. * * <p>NOTE: See more recent W3C note: http://www.w3.org/TR/2008/NOTE-leiri-20081103/ * * @param uriString URI to encode * @param processSpace whether to process the space character or leave it unchanged * @return encoded URI, or null if uriString was null */ public static String encodeHRRI(String uriString, boolean processSpace) { if (uriString == null) return null; // Note that the XML Schema spec says "Spaces are, in principle, allowed in the ·lexical space· // of anyURI, // however, their use is highly discouraged (unless they are encoded by %20).". // We assume that we never want leading or trailing spaces. You can use %20 if you really want // this. uriString = uriString.trim(); // We try below to follow the "Human Readable Resource Identifiers" RFC, in draft as of // 2007-06-06. // * the control characters #x0 to #x1F and #x7F to #x9F // * space #x20 // * the delimiters "<" #x3C, ">" #x3E, and """ #x22 // * the unwise characters "{" #x7B, "}" #x7D, "|" #x7C, "\" #x5C, "^" #x5E, and "`" #x60 final StringBuilder sb = new StringBuilder(uriString.length() * 2); for (int i = 0; i < uriString.length(); i++) { final char currentChar = uriString.charAt(i); if (currentChar >= 0 && (currentChar <= 0x1f || (processSpace && currentChar == 0x20) || currentChar == 0x22 || currentChar == 0x3c || currentChar == 0x3e || currentChar == 0x5c || currentChar == 0x5e || currentChar == 0x60 || (currentChar >= 0x7b && currentChar <= 0x7d) || (currentChar >= 0x7f && currentChar <= 0x9f))) { sb.append('%'); sb.append(NumberUtils.toHexString((byte) currentChar).toUpperCase()); } else { sb.append(currentChar); } } return sb.toString(); } public static class DynamicResource { private String uri; private String filename; private String contentType; private long size; private long lastModified; public DynamicResource( String uri, String filename, String contentType, long size, long lastModified) { this.uri = uri; this.filename = filename; this.contentType = contentType; this.size = size; this.lastModified = lastModified; } public String getURI() { return uri; } public String getFilename() { return filename; } public String getContentType() { return contentType; } public long getSize() { return size; } public long getLastModified() { return lastModified; } } }