/* * Copyright (C) 2000-2015 aw2.0 LTD * * This file is part of Open BlueDragon (OpenBD) CFML Server Engine. * * OpenBD is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * Free Software Foundation,version 3. * * OpenBD 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 OpenBD. If not, see http://www.gnu.org/licenses/ * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining * it with any of the JARS listed in the README.txt (or a modified version of * (that library), containing parts covered by the terms of that JAR, the * licensors of this Program grant you additional permission to convey the * resulting work. * README.txt @ http://www.openbluedragon.org/license/README.txt * * http://www.openbd.org/ * $Id: cfEngine.java 2486 2015-01-22 03:22:37Z alan $ */ package com.naryx.tagfusion.cfm.engine; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; import java.util.Properties; import java.util.ResourceBundle; import java.util.Vector; import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.vfs.FileSystemManager; import org.aw20.io.StreamUtil; import org.aw20.net.HttpGet; import com.bluedragon.journal.JournalManager; import com.bluedragon.journal.JournalSession; import com.bluedragon.platform.Platform; import com.bluedragon.plugin.PluginManager; import com.bluedragon.plugin.RequestListener; import com.nary.io.FileUtils; import com.nary.io.StreamUtils; import com.nary.util.AverageTracker; import com.nary.util.FastMap; import com.nary.util.Localization; import com.naryx.tagfusion.cfm.application.ScriptProtect; import com.naryx.tagfusion.cfm.cache.CacheFactory; import com.naryx.tagfusion.cfm.file.cfFile; import com.naryx.tagfusion.cfm.file.cfmlFileCache; import com.naryx.tagfusion.cfm.sql.ODBCNativeLib; import com.naryx.tagfusion.cfm.sql.cfDataSourceStatus; import com.naryx.tagfusion.cfm.sql.pool.DataSourcePoolFactory; import com.naryx.tagfusion.cfm.tag.tagChecker; import com.naryx.tagfusion.cfm.tag.ext.thread.cfThreadRunner; import com.naryx.tagfusion.expression.compile.expressionEngine; import com.naryx.tagfusion.expression.function.string.deserializejson; import com.naryx.tagfusion.xmlConfig.xmlCFML; import com.naryx.tagfusion.xmlConfig.xmlConfigManagerFactory; public class cfEngine extends Object implements cfEngineMBean { public static final String DEFAULT_BUFFERSIZE = "0"; public static final String DEFAULT_SUPPRESS_WHITESPACE = "false"; public static final String DEFAULT_ERROR_HANDLER = ""; public static final String DEFAULT_MISSING_TEMPLATE_HANDLER = ""; public static final String DEFAULT_DEBUG = "true"; public static final String DEFAULT_CHARSET = "UTF-8"; public static final String DEFAULT_SCRIPTPROTECT = "false"; public static final String DEFAULT_SCRIPTSRC = "/bluedragon/scripts/"; public static final String DEFAULT_ASSERT = "false"; public static final String DEFAULT_STRICT_FUNCTIONSCOPEDVARS = "false"; public static final String DEFAULT_STRICT_ARRAYBYREFERENCE = "false"; public static final String PRODUCT_NAME = "BlueDragon"; public static String PRODUCT_VERSION; public static String PRODUCT_RELEASEDATE; public static String PRODUCT_STATE; public static String BUILD_ISSUE; public static boolean WINDOWS; public static cfEngine thisInstance = null; public static ServletContext thisServletContext; public tagChecker TagChecker = null; public long startTime = 0; public long totalBytesSent = 0, totalRequests = 0; private String nativeLibDirectory; private File xmlFileLocation = null; private final Map<String, cfDataSourceStatus> dataSourceStatus = new FastMap<String, cfDataSourceStatus>(); private xmlCFML systemParameters; // this is the in-memory representation of bluedragon.xml private boolean bDebugOutputEnabled = true, bAssertionsEnabled = false, bSuppressWhitespace = false, bLegacyFormValidation = true, bCombinedFormUrlScope = false, bFunctionScopedVariables = false, bStrictArrayPassByReference = false, bMainFormUrlCase = false, bDebugerOutputEnabled = false, bCFoutputShorthand = false, bScriptProtect = false, bRemoteOpenBDXML = false; private Vector<engineListener> engineListeners; private ResourceBundle runtimeMessages; public AverageTracker avgTracker; public JournalManager journalManager; private String defaultCharset, webResourcePath; private final com.bluedragon.plugin.PluginManager pluginManager; private com.bluedragon.plugin.RequestListener requestListener = null; private int[] debugIPsAsInts; public static Platform thisPlatform; public static FileSystemManager vfsManager; public static DataSourcePoolFactory dataSourcePoolFactory; public static boolean bEngineActive = true; public static String OpenBDLogoDataUri = null; public static String DefaultJSONReturnCase = "maintain"; public static String DefaultJSONReturnDate = "long"; public static AtomicInteger sessionCounter = new AtomicInteger(); static { WINDOWS = System.getProperty( "os.name" ).startsWith( "Windows" ); PRODUCT_VERSION = "Unknown"; PRODUCT_RELEASEDATE = "Unknown"; PRODUCT_STATE = "Unknown"; try { InputStream in = cfEngine.class.getResourceAsStream( "/openbd.properties" ); if ( in != null ) { Properties props = new Properties(); props.load( in ); PRODUCT_VERSION = props.getProperty( "version", PRODUCT_VERSION ); BUILD_ISSUE = props.getProperty( "builddate", "" ); PRODUCT_STATE = props.getProperty( "state", PRODUCT_STATE ); PRODUCT_RELEASEDATE = props.getProperty( "releasedate", PRODUCT_RELEASEDATE ); } } catch ( Exception e ) {} } /** * ------------------------------------------------------ * Initialisation method * * Run once and initialises all the sub-components of the main engine */ private cfEngine( ServletConfig config ) throws ServletException { thisInstance = this; thisServletContext = config.getServletContext(); // Setup the main marker class for the platform flag String bluedragonXmlParm = config.getInitParameter( "BLUEDRAGON_XML" ); try { thisPlatform = (Platform) Class.forName( "com.bluedragon.platform.java.JavaPlatform" ).newInstance(); if ( ( bluedragonXmlParm != null ) && ( bluedragonXmlParm.length() > 0 ) ) xmlFileLocation = getOpenBDXmlFile( bluedragonXmlParm ); } catch ( Exception e ) { throw new ServletException( PRODUCT_NAME + ": " + e ); } // Load in the main XML configuration file try { setSystemParameters( xmlConfigManagerFactory.createXmlConfigManager( xmlFileLocation ).getXMLCFML() ); engineListeners = new Vector<engineListener>(); } catch ( Exception E ) { thisInstance = null; System.out.println( PRODUCT_NAME + ": Error " + E + " loading: BLUEDRAGON_XML=" + bluedragonXmlParm ); throw new ServletException( PRODUCT_NAME + ": Error occurred loading: BLUEDRAGON_XML=" + bluedragonXmlParm + ", " + E ); } // Load in the image try { InputStream in = this.getClass().getResourceAsStream( "openbdlogo.txt" ); if ( in != null ) OpenBDLogoDataUri = StreamUtils.readToString( in ); } catch ( NullPointerException e1 ) { OpenBDLogoDataUri = null; } catch ( IOException e1 ) { OpenBDLogoDataUri = null; } // Initialise the Platform thisPlatform.init( config ); // Get a handle to the underlying FileSystem manager vfsManager = thisPlatform.getFileIO().vfsManager(); // Initialize the Datasource dataSourcePoolFactory = new DataSourcePoolFactory(); try { // Load up the runtimeErrorMessages runtimeMessages = ResourceBundle.getBundle( "com.naryx.tagfusion.cfm.engine.exceptionMessage" ); log( runtimeMessages.getString( "cfEngine.welcomeMessage" ) ); log( "Product Version: " + PRODUCT_VERSION ); log( "Build date: " + BUILD_ISSUE ); // TagChecker TagChecker = new tagChecker(); // Core Expression expressionEngine.init(); // fileCache initialisation cfmlFileCache.init( thisServletContext, getSystemParameters() ); // CFX initalization com.naryx.tagfusion.cfx.cfCFX.init( getSystemParameters() ); // CFCs ComponentFactory.init( getSystemParameters() ); startTime = System.currentTimeMillis(); } catch ( Exception E ) { log( PRODUCT_NAME + " Engine Failed to load:" + E.getMessage() ); E.printStackTrace(); thisInstance = null; throw new ServletException( "Failed to initialise the cfEngine: " + E.getMessage() ); } // Create the plugin Manager and load up any extensions pluginManager = new PluginManager( getSystemParameters() ); // Set the Default Character set setDefaultCharset(); // Set the Web Resource path setWebResourcePath(); // Set the native library path; must be done before tags are initialised setNativeLibDirectory(); // Initialise any of the tags try { TagChecker.initialiseTags( getSystemParameters() ); } catch ( Throwable t ) { t.printStackTrace(); thisInstance = null; log( PRODUCT_NAME + " Engine Failed to initialise tags:" + t.getMessage() ); com.nary.Debug.printStackTrace( t ); throw new ServletException( PRODUCT_NAME + " Engine Failed to initialise tags: " + t.getMessage() ); } // Set the startup engine flags String ipList = getSystemParameters().getString( "server.debugoutput.ipaddresses" ); if ( ( ipList == null ) || ( ipList.length() == 0 ) ) debugIPsAsInts = new int[0]; else debugIPsAsInts = cfSession.DecodeIPs( ipList ); setDebugOutputFlag(); setScriptProtectFlag(); setStrictFlags(); setAssertionsFlag(); setCombinedFormUrlFlag(); setLegacyFormValidation(); setSuppressWhiteSpace(); setDefaultBufferSize(); setDefaultJSONFlags(); setFormUrlCaseMaintainedFlag(); setCFOutpuShorthand(); // Only update the bluedragon.xml file if flagged to do so if ( getSystemParameters().getBoolean( "server.system.rewritebluedragonxml", true ) && !bluedragonXmlParm.startsWith( "http" ) ) { try { writeXmlFile( getSystemParameters(), false ); } catch ( cfmRunTimeException e ) { log( PRODUCT_NAME + " " + e.getMessage() ); } } // Auto-configure ODBC datasources (notifyListeners=false, autoConfig=true) autoConfigOdbcDataSources( false, true ); // Setup/clean the dynamic web service cache (and any other resources) try { cfWebServices.initialize( config ); } catch ( Exception e ) { throw new ServletException( PRODUCT_NAME + " Engine Failed to initialise Web Services: " + e.getMessage() ); } startRequestStats(); // Setup the Journal Manager journalManager = new JournalManager(); log( runtimeMessages.getString( "cfEngine.serverStarted" ) ); // The Engine is ready for requests, so lets call the ServerCFC handling new ServerCFC().onServerStart( getSystemParameters() ); } private File getOpenBDXmlFile( String bluedragonXmlParm ) throws IOException { if ( bluedragonXmlParm.startsWith( "http://" ) || bluedragonXmlParm.startsWith( "https://" ) ) { bRemoteOpenBDXML = true; File locationXMLFile = File.createTempFile( "openbd", ".xml" ); System.out.println( "Loading Remote bluedragon.xml from: " + bluedragonXmlParm ); String remoteXML = HttpGet.doGet( bluedragonXmlParm ).getBodyAsString(); FileWriter fos = null; try { fos = new FileWriter( locationXMLFile ); fos.write( remoteXML ); fos.flush(); } finally { StreamUtil.closeStream( fos ); } return locationXMLFile; } else { return getResolvedFile( bluedragonXmlParm ); } } public static synchronized void init( ServletConfig config ) throws ServletException { if ( cfEngine.thisInstance == null ) new cfEngine( config ); } private void setSystemParameters( xmlCFML xmlcfml ) { systemParameters = xmlcfml; } public xmlCFML getSystemParameters() { return systemParameters; } public static void destroy() { // If the BlueDragon engine has already been destroyed then just return. if ( cfEngine.thisInstance == null ) return; log( PRODUCT_NAME + " is being shut down ... " ); bEngineActive = false; cfThreadRunner.stopAllThreads(); notifyAllListenersShutdown(); thisInstance.pluginManager.shutdown(); com.naryx.tagfusion.cfm.engine.variableStore.shutdown(); cfEngine.thisPlatform.destroy(); // Close off all the logging around the system com.nary.util.LogFile.closeAll(); log( "All logging has been shutdown" ); dataSourcePoolFactory.close(); CacheFactory.shutdown(); log( PRODUCT_NAME + " has been successfully shutdown" ); com.nary.Debug.setFilename( null ); thisInstance = null; } public static Map<String, cfDataSourceStatus> getDataSourceStatus() { return cfEngine.thisInstance.dataSourceStatus; } public static int[] getDebugIPs() { return cfEngine.thisInstance.debugIPsAsInts; } public static xmlCFML getConfig() { return cfEngine.thisInstance.getSystemParameters(); } // -------------------------------------------------------- // --] Engine Listener Methods // -------------------------------------------------------- public static final void registerEngineListener( engineListener _new ) { cfEngine.thisInstance.engineListeners.addElement( _new ); } private static final void notifyAllListenersShutdown() { // This method may be called before the static instance variable has been initialized; for example // when a shutdown occurs before any requests have been processed. To avoid the NPE, test the // state of thisInstance first. if ( cfEngine.thisInstance != null ) { Enumeration<engineListener> E = cfEngine.thisInstance.engineListeners.elements(); while ( E.hasMoreElements() ) E.nextElement().engineShutdown(); } } public static final void notifyAllListenersAdmin( xmlCFML newConfig ) { cfEngine.thisInstance.setSystemParameters( newConfig ); Enumeration<engineListener> E = cfEngine.thisInstance.engineListeners.elements(); while ( E.hasMoreElements() ) E.nextElement().engineAdminUpdate( newConfig ); thisPlatform.engineAdminUpdate(); cfEngine.thisInstance.setDebugOutputFlag(); cfEngine.thisInstance.setSuppressWhiteSpace(); cfEngine.thisInstance.setLegacyFormValidation(); cfEngine.thisInstance.setDefaultCharset(); cfEngine.thisInstance.setDefaultBufferSize(); cfmlFileCache.flushCache(); } private void setDefaultBufferSize() { // Set the response buffer size from the configuration. The default // is 0 which means to buffer the entire page. try { int defaultBufferSize = getSystemParameters().getInt( "server.system.buffersize", 0 ) * 1024; if ( defaultBufferSize == 0 ) defaultBufferSize = cfHttpServletResponse.UNLIMITED_SIZE; cfHttpServletResponse.setUserSize( defaultBufferSize ); } catch ( Exception e ) { getSystemParameters().setData( "server.system.buffersize", DEFAULT_BUFFERSIZE ); cfHttpServletResponse.setUserSize( cfHttpServletResponse.UNLIMITED_SIZE ); log( "response buffer set to entire page." ); } } private void setDefaultJSONFlags() { cfEngine.DefaultJSONReturnCase = getSystemParameters().getString( "server.system.jsoncase", "maintain" ).toLowerCase().trim(); if ( !"maintain".equals( cfEngine.DefaultJSONReturnCase ) && !"lower".equals( cfEngine.DefaultJSONReturnCase ) && !"upper".equals( cfEngine.DefaultJSONReturnCase ) && !"true".equals( cfEngine.DefaultJSONReturnCase ) && !"false".equals( cfEngine.DefaultJSONReturnCase ) ) cfEngine.DefaultJSONReturnCase = "maintain"; cfEngine.DefaultJSONReturnDate = getSystemParameters().getString( "server.system.jsondate", "long" ).toLowerCase().trim(); if ( !"long".equals( cfEngine.DefaultJSONReturnDate ) && !"http".equals( cfEngine.DefaultJSONReturnDate ) && !"json".equals( cfEngine.DefaultJSONReturnDate ) && !"mongo".equals( cfEngine.DefaultJSONReturnDate ) && !"cfml".equals( cfEngine.DefaultJSONReturnDate ) ) cfEngine.DefaultJSONReturnDate = "long"; log( "cfEngine: JSON Encoding Defaults: server.system.jsoncase=" + cfEngine.DefaultJSONReturnCase + "; server.system.jsondate=" + cfEngine.DefaultJSONReturnDate ); } private void setSuppressWhiteSpace() { bSuppressWhitespace = getSystemParameters().getBoolean( "server.system.whitespacecomp", Boolean.valueOf( DEFAULT_SUPPRESS_WHITESPACE ).booleanValue() ); } // get suppress whitespace setting as configured in bluedragon.xml public static boolean getSuppressWhiteSpaceDefault() { return thisInstance.bSuppressWhitespace; } /** * Returns back the alternative path for the JAR files * * @return */ public static String getAltLibPath() { String altpath = thisInstance.getSystemParameters().getString( "server.system.libpath" ); if ( altpath != null ) { File f = new File( altpath ); if ( f.isDirectory() && f.exists() ) { String t = f.getAbsolutePath(); if ( !t.endsWith( File.separator ) ) t = t + File.separator; return t; } } return altpath; } public static File getXmlFileName() { return cfEngine.thisInstance.xmlFileLocation; } /* * getResolvedFile * * This method uses context.getRealPath() so it should only be called at init * time and not at request time. You can call it at request time if you know * that with BD Server and JX you want the file to be resolved relative to the * built-in web server's wwwroot directory. An example of this is the * CFXClassLoader.loadTagsFolderClass(). */ public static File getResolvedFile( String directory ) { if ( ( directory == null ) || ( directory.length() == 0 ) ) return null; if ( directory.length() > 0 && directory.charAt( 0 ) == '/' ) { // --[ Relative path String realPath = FileUtils.getRealPath( directory ); return ( realPath == null ? null : new File( realPath ) ); } else if ( directory.length() > 0 && directory.charAt( 0 ) == '$' ) { // --[ Real path return new File( directory.substring( 1 ) ); } else { // --[ Real path return new File( directory ); } } public static final tagChecker getTagChecker() { return cfEngine.thisInstance.TagChecker; } public static final String getNativeLibDirectory() { return cfEngine.thisInstance.nativeLibDirectory; } public static final String getMessage( String id ) { try { return cfEngine.thisInstance.runtimeMessages.getString( id ); } catch ( MissingResourceException e ) { return "Unrecognized error code: " + id; } } public static final String getMessage( String id, String values[] ) { try { String message = cfEngine.thisInstance.runtimeMessages.getString( id ); if ( values == null || message == null ) return message; for ( int x = 0; x < values.length; x++ ) message = com.nary.util.string.replaceString( message, "%" + ( x + 1 ), values[x] ); return message; } catch ( MissingResourceException e ) { return "Unrecognized error code: " + id; } } public final static boolean isFormUrlScopeCombined() { return cfEngine.thisInstance.bCombinedFormUrlScope; } public final static boolean isCFOutputShorthand() { return cfEngine.thisInstance.bCFoutputShorthand; } public final static boolean isFormUrlCaseMaintained() { return cfEngine.thisInstance.bMainFormUrlCase; } public final static boolean isDebugOutputEnabled() { return cfEngine.thisInstance.bDebugOutputEnabled; } public final static boolean isScriptProtect() { return cfEngine.thisInstance.bScriptProtect; } public final static boolean isDebuggerOutputEnabled() { return cfEngine.thisInstance.bDebugerOutputEnabled; } public final static boolean isAssertionsEnabled() { return cfEngine.thisInstance.bAssertionsEnabled; } public final static boolean isStrictPassArrayByReference() { return cfEngine.thisInstance.bStrictArrayPassByReference; } public final static boolean isFunctionScopedVariables() { return cfEngine.thisInstance.bFunctionScopedVariables; } /** * The encoding value should be used internally. */ public static String getDefaultEncoding() { return Localization.convertCharSetToCharEncoding( cfEngine.getDefaultCharset() ); } public static String getDefaultCharset() { return cfEngine.thisInstance.defaultCharset; } /** * The charset value should be used in the request/response content-type * header. */ private void setDefaultCharset() { defaultCharset = getSystemParameters().getString( "server.system.defaultcharset", DEFAULT_CHARSET ); log( "cfEngine: Using default character encoding " + defaultCharset ); } private void setWebResourcePath() { webResourcePath = getSystemParameters().getString( "server.system.resourcepath", "/WEB-INF/webresources" ); if ( webResourcePath.endsWith( "/" ) ) webResourcePath = webResourcePath.substring( 0, webResourcePath.length() - 1 ); webResourcePath = getResolvedFile( webResourcePath ).toString(); log( "cfEngine: WebResourcePath " + webResourcePath ); } private void setDebugOutputFlag() { bDebugOutputEnabled = getSystemParameters().getBoolean( "server.system.debug", Boolean.valueOf( DEFAULT_DEBUG ).booleanValue() ); log( "cfEngine: [server.system.debug] Show Debug output on error? " + bDebugOutputEnabled ); bDebugerOutputEnabled = getSystemParameters().getBoolean( "server.debugoutput.enabled", Boolean.valueOf( false ).booleanValue() ); log( "cfEngine: [server.debugoutput.enabled] Show Debugger output? " + bDebugerOutputEnabled ); } private void setScriptProtectFlag() { bScriptProtect = getSystemParameters().getBoolean( "server.system.scriptprotect", Boolean.valueOf( DEFAULT_SCRIPTPROTECT ).booleanValue() ); log( "cfEngine: [server.system.scriptprotect] ScriptProtect " + ( bScriptProtect ? "enabled" : "disabled" ) ); ScriptProtect.init( getConfig() ); } private void setAssertionsFlag() { bAssertionsEnabled = getSystemParameters().getBoolean( "server.system.assert", Boolean.valueOf( DEFAULT_ASSERT ).booleanValue() ); log( "cfEngine: [server.system.assert] Assertions " + ( bAssertionsEnabled ? "enabled" : "disabled" ) ); } private void setCombinedFormUrlFlag() { bCombinedFormUrlScope = getSystemParameters().getBoolean( "server.system.formurlcombined", false ); log( "cfEngine: [server.system.formurlcombined] Combined Form/Url Scope? " + bCombinedFormUrlScope ); } private void setCFOutpuShorthand() { bCFoutputShorthand = getSystemParameters().getBoolean( "server.system.cfoutputshorthand", false ); log( "cfEngine: [server.system.cfoutputshorthand] <%= ... %> ? " + bCFoutputShorthand ); } private void setFormUrlCaseMaintainedFlag() { bMainFormUrlCase = getSystemParameters().getBoolean( "server.system.formurlmaintaincase", false ); log( "cfEngine: [server.system.formurlmaintaincase] Case Form/Url maintained? " + bMainFormUrlCase ); } private void setStrictFlags() { bStrictArrayPassByReference = getSystemParameters().getBoolean( "server.system.strictarraypassbyreference", Boolean.valueOf( DEFAULT_STRICT_ARRAYBYREFERENCE ).booleanValue() ); log( "cfEngine: [server.system.strictarraypassbyreference] Strict Mode: ArrayPassByReference? " + bStrictArrayPassByReference ); bFunctionScopedVariables = getSystemParameters().getBoolean( "server.system.functionscopedvariables", Boolean.valueOf( DEFAULT_STRICT_FUNCTIONSCOPEDVARS ).booleanValue() ); log( "cfEngine: [server.system.functionscopedvariables] Strict Mode: FunctionScopedVariables? " + bFunctionScopedVariables ); } private void setLegacyFormValidation() { bLegacyFormValidation = getSystemParameters().getBoolean( "server.system.legacyformvalidation", true ); log( "cfEngine: [server.system.legacyformvalidation] Legacy server side form validation? " + bLegacyFormValidation ); } private void setNativeLibDirectory() { nativeLibDirectory = getSystemParameters().getString( "server.system.nativelibdir" ); if ( nativeLibDirectory == null ) { log( PRODUCT_NAME + " failed to set NativeLibDirectory. Check the nativelibdir is defined in the configuration file." ); } else if ( !nativeLibDirectory.endsWith( "/" ) ) { nativeLibDirectory = nativeLibDirectory + "/"; } String osArch = System.getProperty( "os.arch" ); if ( ( osArch != null ) && ( osArch.indexOf( "64" ) != -1 ) ) { nativeLibDirectory = nativeLibDirectory + "x64/"; } log( "cfEngine: server.system.nativelibdir=[" + nativeLibDirectory + "]" ); } public static void autoConfigOdbcDataSources() { autoConfigOdbcDataSources( true, false ); } public static void autoConfigOdbcDataSources( boolean notifyListeners, boolean autoConfig ) { // currently only supported on Windows if ( !WINDOWS ) return; try { xmlCFML config = getConfig(); // If this is an auto-config instead of an odbc refresh // then check if auto-config is disabled in bluedragon.xml if ( autoConfig ) { if ( !config.getBoolean( "server.cfquery.autoconfig-odbc", true ) ) { log( "Auto-configure of ODBC datasources is disabled" ); return; } } log( "cfEngine: [server.cfquery.autoconfig-odbc] Auto-configure of ODBC datasources is enabled" ); boolean writeXmlFile = false; String[] odbcDataSources = ODBCNativeLib.getOdbcDataSources(); for ( int i = 0; i < odbcDataSources.length; i += 2 ) { String dsnName = odbcDataSources[i]; String description = odbcDataSources[i + 1]; String dsnKey = "server.cfquery.datasource[" + dsnName.toLowerCase() + "]"; // test to see if datasource name already configured String currentDescr = config.getString( dsnKey + ".description" ); if ( currentDescr == null ) { config.setData( dsnKey + ".name", dsnName ); config.setData( dsnKey + ".logintimeout", "120" ); config.setData( dsnKey + ".connectiontimeout", "120" ); config.setData( dsnKey + ".username", "" ); config.setData( dsnKey + ".hoststring", ODBCNativeLib.URL_PREFIX + dsnName ); config.setData( dsnKey + ".databasename", dsnName ); config.setData( dsnKey + ".drivername", ODBCNativeLib.DRIVER_CLASS ); config.setData( dsnKey + ".password", "" ); config.setData( dsnKey + ".description", description ); config.setData( dsnKey + ".maxconnections", "24" ); config.setData( dsnKey + ".sqlselect", "true" ); config.setData( dsnKey + ".sqldelete", "true" ); config.setData( dsnKey + ".sqlinsert", "true" ); config.setData( dsnKey + ".sqlupdate", "true" ); config.setData( dsnKey + ".sqlstoredprocedures", "true" ); config.setData( dsnKey + ".drivertype", "3" ); config.setData( dsnKey + ".servername", "" ); config.setData( dsnKey + ".port", "" ); cfEngine.getDataSourceStatus().put( dsnName.toLowerCase(), new cfDataSourceStatus() ); log( "Auto-configured ODBC datasource: " + dsnName ); writeXmlFile = true; } } if ( writeXmlFile ) cfEngine.writeXmlFile( config, notifyListeners ); } catch ( UnsatisfiedLinkError ignore ) {} catch ( Exception e ) { log( "ODBC auto-configuration error: " + e.getMessage() ); com.nary.Debug.printStackTrace( e ); } } public static void writeXmlFile( xmlCFML newXmlCFML ) throws cfmRunTimeException { writeXmlFile( newXmlCFML, true ); } public static void reloadXmlFile() throws cfmRunTimeException { File xmlFileName = getXmlFileName(); if ( xmlFileName != null ) { try { thisInstance.setSystemParameters( xmlConfigManagerFactory.createXmlConfigManager( xmlFileName ).getXMLCFML() ); } catch ( Exception E ) { cfCatchData catchData = new cfCatchData(); catchData.setMessage( "failed to load configuration file" ); catchData.setExtendedInfo( E.getMessage() ); throw new cfmRunTimeException( catchData ); } notifyAllListenersAdmin( thisInstance.getSystemParameters() ); } else { cfCatchData catchData = new cfCatchData(); catchData.setMessage( "failed to find configuration file" ); throw new cfmRunTimeException( catchData ); } } public synchronized static void writeXmlFile( xmlCFML newXmlCFML, boolean notifyListeners ) throws cfmRunTimeException { if ( thisInstance.bRemoteOpenBDXML ) { log( "Attempted to update a remote bluedragon.xml; operation failed" ); return; } File xmlFileName = getXmlFileName(); if ( ( xmlFileName != null ) && ( xmlFileName.canWrite() ) ) { // delete the oldest (10th) backup; increment the numbers of the remaining backups for ( int backupNo = 10; backupNo > 0; backupNo-- ) { File backupFile = new File( xmlFileName + ".bak." + backupNo ); if ( backupFile.exists() ) { if ( backupNo == 10 ) { backupFile.delete(); } else { backupFile.renameTo( new File( xmlFileName + ".bak." + ( backupNo + 1 ) ) ); } } } // clean up older backup files for ( int backupNo = 11; true; backupNo++ ) { File backupFile = new File( xmlFileName + ".bak." + backupNo ); if ( backupFile.exists() ) { backupFile.delete(); } else { break; } } // write back out the xml file to the file, saving the old one File xmlFile = xmlFileName; File newFile = new File( xmlFileName + ".bak.1" ); // Put in the last updated newXmlCFML.setData( "server.system.lastupdated", com.nary.util.Date.formatDate( System.currentTimeMillis(), "dd/MMM/yyyy HH:mm.ss", Locale.US ) ); newXmlCFML.setData( "server.system.lastfile", newFile.toString() ); // Rename the file xmlFile.renameTo( newFile ); try { newXmlCFML.writeTo( xmlFile ); } catch ( IOException e ) { cfCatchData catchData = new cfCatchData(); catchData.setMessage( "failed to write configuration file, " + e.toString() ); throw new cfmRunTimeException( catchData ); } } else { // NOTE: this will happen with BD J2EE when it is running in a WAR file, // or when the bluedragon.xml // file is marked read-only, or when something goes seriously wrong at BD // init time. cfCatchData catchData = new cfCatchData(); catchData.setMessage( "failed to write configuration file" ); throw new cfmRunTimeException( catchData ); } if ( notifyListeners ) { notifyAllListenersAdmin( newXmlCFML ); } } // -------------------------------------------------------- // --] Main Service method // -------------------------------------------------------- public static void service( HttpServletRequest req, HttpServletResponse res ) throws ServletException { Thread.currentThread().setName( req.getRequestURI() ); thisInstance.totalRequests++; if ( thisInstance.avgTracker != null ) { thisInstance.avgTracker.begin(); } cfSession _Session = new cfSession( req, res, thisServletContext ); JournalSession journalSession = thisInstance.journalManager.getJournalSession( req ); cfFile requestFile = null; try { // If we are instrumenting this request, then we want to use that and disable any other plugin that maybe used if ( journalSession != null ) journalSession.requestStart( _Session ); else if ( thisInstance.requestListener != null ) thisInstance.requestListener.requestStart( _Session ); _Session.setSuppressWhiteSpace( thisInstance.bSuppressWhitespace ); _Session.checkAndDecodePost( thisInstance.bLegacyFormValidation ); // Load in the main request file try { requestFile = _Session.getRequestFile(); } catch ( cfmBadFileException bfe ) { // attempt to invoke onMissingTemplate handler for file-not-found if ( bfe.fileNotFound() && _Session.onMissingTemplate() ) { _Session.pageEnd(); return; } throw bfe; } // Render the Application.cfm or Application.cfc _Session.onRequestStart( requestFile ); // Render the main request file _Session.onRequest( requestFile ); // Render the 'OnRequestEnd.cfm' file _Session.onRequestEnd( requestFile.getURI() ); // Send all data, including any that may be in the cache straight to the client _Session.pageEnd(); } catch ( cfmAbortException ACF ) { // Do nothing. Don't print anything out to the Session _Session.pageEnd(); } catch ( cfmBadFileException BF ) { BF.handleException( requestFile, _Session ); // If we are instrumenting this request, then we want to use that and disable any other plugin that maybe used if ( journalSession != null ) journalSession.requestBadFileException( BF, _Session ); else if ( thisInstance.requestListener != null ) thisInstance.requestListener.requestBadFileException( BF, _Session ); } catch ( cfmRunTimeException CF ) { CF.handleException( _Session ); // If a plugin is listening to this request if ( journalSession != null ) journalSession.requestRuntimeException( CF, _Session ); else if ( thisInstance.requestListener != null ) thisInstance.requestListener.requestRuntimeException( CF, _Session ); } catch ( Throwable E ) { new cfmRunTimeException( _Session, E ).handleException( _Session ); } finally { // no matter what happens, we must ensure this gets called if ( thisInstance.avgTracker != null ) thisInstance.avgTracker.end(); thisInstance.totalBytesSent += _Session.getBytesSent(); // If we are instrumenting this request, then we want to use that and disable any other plugin that maybe used if ( journalSession != null ) journalSession.requestEnd( _Session ); else if ( thisInstance.requestListener != null ) thisInstance.requestListener.requestEnd( _Session ); _Session.close(); Thread.currentThread().setName( "no-request" ); } } /** * serviceCfcMethod - this method is invoked when a url like the following is * received: * * /xxx.cfc?method= <method name>&arg1=value1&arg2=value2 * * It invokes the specified method which is defined in the CFC with the * specified arguments. */ public static void serviceCfcMethod( HttpServletRequest req, HttpServletResponse res ) { Thread.currentThread().setName( req.getRequestURI() ); thisInstance.totalRequests++; cfSession _Session = new cfSession( req, res, thisServletContext ); JournalSession journalSession = thisInstance.journalManager.getJournalSession( req ); cfFile svrFile = null; boolean defaultExceptionHandling = true; boolean serializeQueryByColumns = false; try { _Session.setSuppressWhiteSpace( thisInstance.bSuppressWhitespace ); // If we are instrumenting this request, then we want to use that and disable any other plugin that maybe used if ( journalSession != null ) journalSession.requestStart( _Session ); else if ( thisInstance.requestListener != null ) thisInstance.requestListener.requestStart( _Session ); _Session.checkAndDecodePost( false ); // Load in the main request file svrFile = _Session.getRequestFile(); _Session.onRequestStart( svrFile ); // Extract the component name from the URI (it's the filename minus the extension) String compName = svrFile.getURI(); int pos = compName.lastIndexOf( '/' ); if ( pos != -1 ) compName = compName.substring( pos + 1 ); pos = compName.lastIndexOf( '.' ); if ( pos != -1 ) compName = compName.substring( 0, pos ); // Extract the method name and arguments String methodName = null; String formatMethod = null; String jsonpCallback = null; String jsonCase = null; boolean jsonEncodedParams = false; cfArgStructData arguments = new cfArgStructData(); String[] bdParams = null; Object[] paramNames; cfFormData formData = (cfFormData) _Session.getQualifiedData( variableStore.FORM_SCOPE ); paramNames = formData.keys(); for ( int i = 0; i < paramNames.length; i++ ) { String name = (String) paramNames[i]; // need uppercase version to account for formurlmaintaincase String ucName = name.toUpperCase(); cfData value = formData.getData( name ); if ( ucName.equals( "METHOD" ) ) methodName = value.getString(); else if ( ucName.equals( "__BDNODEBUG" ) ) defaultExceptionHandling = false; else if ( ucName.equals( "__BDJSONENCODED" ) ) jsonEncodedParams = true; else if ( ucName.equals( "__BDJSONCASE" ) ) jsonCase = value.getString().toLowerCase(); else if ( ucName.equals( "__BDRETURNFORMAT" ) ) formatMethod = value.getString().toUpperCase(); else if ( ucName.equals( "__BDPARAMS" ) ) bdParams = value.getString().split( "," ); else if ( ucName.equals( "__BDQUERYFORMAT" ) ) serializeQueryByColumns = ( value.getString().equalsIgnoreCase( "column" ) ); else if ( !ucName.equals( "FIELDNAMES" ) ) arguments.setData( (String) paramNames[i], value ); } cfUrlData urlData = (cfUrlData) _Session.getQualifiedData( variableStore.URL_SCOPE ); paramNames = urlData.keys(); for ( int i = 0; i < paramNames.length; i++ ) { String name = (String) paramNames[i]; // need uppercase version to account for formurlmaintaincase String ucName = name.toUpperCase(); cfData value = urlData.getData( name ); if ( ucName.equals( "METHOD" ) ) methodName = value.getString(); else if ( ucName.equals( "CALLBACK" ) ) jsonpCallback = value.getString(); else if ( ucName.equals( "__BDNODEBUG" ) ) defaultExceptionHandling = false; else if ( ucName.equals( "__BDJSONENCODED" ) ) jsonEncodedParams = true; else if ( ucName.equals( "__BDJSONCASE" ) ) jsonCase = value.getString().toLowerCase(); else if ( ucName.equals( "__BDPARAMS" ) ) bdParams = value.getString().split( "," ); else if ( ucName.equals( "__BDRETURNFORMAT" ) ) formatMethod = value.getString().toUpperCase(); else if ( ucName.equals( "__BDQUERYFORMAT" ) ) serializeQueryByColumns = ( value.getString().equalsIgnoreCase( "column" ) ); else arguments.setData( name, value ); } // Make sure a method name was specified if ( methodName == null ) { cfCatchData catchData = new cfCatchData(); catchData.setMessage( "the request must contain a METHOD parameter" ); throw new cfmRunTimeException( catchData ); } // Make sure this is a valid JSONP call if ( formatMethod != null && formatMethod.equals( "JSONP" ) && jsonpCallback == null ) { cfCatchData catchData = new cfCatchData(); catchData.setMessage( "the request must contain a CALLBACK parameter if using JSONP" ); throw new cfmRunTimeException( catchData ); } // The params are JSON encoded, so we need to unwrap them to their rich counter parts if ( jsonEncodedParams ) arguments = deserializejson.transformStructElements( arguments, bdParams ); // Invoke the method _Session.onCFCRequest( svrFile.getURI(), compName, methodName, arguments, formatMethod, serializeQueryByColumns, jsonpCallback, jsonCase ); // Render the 'OnRequestEnd.cfm' file _Session.onRequestEnd( svrFile.getURI() ); _Session.pageEnd(); } catch ( cfmAbortException ACF ) { // Do nothing. Don't print anything out to the Session _Session.pageEnd(); } catch ( cfmBadFileException BF ) { try { _Session.setStatus( 404, "Not Found" ); } catch ( Exception ignore ) {} if ( defaultExceptionHandling ) BF.handleException( svrFile, _Session ); // If we are instrumenting this request, then we want to use that and disable any other plugin that maybe used if ( journalSession != null ) journalSession.requestBadFileException( BF, _Session ); else if ( thisInstance.requestListener != null ) thisInstance.requestListener.requestBadFileException( BF, _Session ); } catch ( cfmRunTimeException CF ) { try { _Session.setStatus( 500, CF.getMessageThenDetail() ); } catch ( Exception ignore ) {} if ( defaultExceptionHandling ) CF.handleException( _Session ); // If we are instrumenting this request, then we want to use that and disable any other plugin that maybe used if ( journalSession != null ) journalSession.requestRuntimeException( CF, _Session ); else if ( thisInstance.requestListener != null ) thisInstance.requestListener.requestRuntimeException( CF, _Session ); } catch ( Throwable E ) { com.nary.Debug.printStackTrace( E ); try { _Session.setStatus( 500, E.getMessage() ); } catch ( Exception ignore ) {} if ( defaultExceptionHandling ) new cfmRunTimeException( _Session, E ).handleException( _Session ); } finally { // If we are instrumenting this request, then we want to use that and disable any other plugin that maybe used if ( journalSession != null ) journalSession.requestEnd( _Session ); else if ( thisInstance.requestListener != null ) thisInstance.requestListener.requestEnd( _Session ); // no matter what happens, we must ensure this gets called _Session.close(); thisInstance.totalBytesSent += _Session.getBytesSent(); Thread.currentThread().setName( "no-request" ); } } /* * Starts the tracking of request stats */ public void startRequestStats() { if ( avgTracker == null ) avgTracker = new AverageTracker( "cfEngine" ); } /* * Stops the tracking of request stats */ public void stopRequestStats() { avgTracker = null; } public AverageTracker getRequestStats() { return avgTracker; } public void registerRequestListener( RequestListener _requestListener ) { requestListener = _requestListener; } public RequestListener getRequestListener() { return requestListener; } public void removeRequestListener() { requestListener = null; } @Override public long getTotalRequests() { return totalRequests; } @Override public long getCurrentRequests() { return ( avgTracker != null ) ? avgTracker.getActiveCount() : -1; } @Override public long getAverageRequestTime() { return ( avgTracker != null ) ? avgTracker.getAverage() : -1; } @Override public long getMaxRequestTime() { return ( avgTracker != null ) ? avgTracker.getMax() : -1; } @Override public long getMinRequestTime() { return ( avgTracker != null ) ? avgTracker.getMin() : -1; } @Override public long getFreeMemory() { return Runtime.getRuntime().freeMemory(); } @Override public long getTotalMemory() { return Runtime.getRuntime().totalMemory(); } @Override public long getTotalBytesSent() { return totalBytesSent; } @Override public String getStartTime() { return new cfDateData( startTime ).getString(); } @Override public int getFunctionCount() { return expressionEngine.getTotalFunctions(); } public static String getWebResourcePath() { return thisInstance.webResourcePath; } @Override public int getTagCount() { return TagChecker.getTotalTags(); } @Override public String getProductVersion() { return PRODUCT_VERSION; } @Override public String getBuildVersion() { return BUILD_ISSUE; } /** * Single point for logging * * @param l */ public static void log( String l ) { thisPlatform.log( l ); } }