/*==========================================================================*\ | $Id: Application.java,v 1.22 2012/06/22 16:23:17 aallowat Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2006-2012 Virginia Tech | | This file is part of Web-CAT. | | Web-CAT is free software; you can redistribute it and/or modify | it under the terms of the GNU Affero General Public License as published | by the Free Software Foundation; either version 3 of the License, or | (at your option) any later version. | | Web-CAT 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 Affero General Public License | along with Web-CAT; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package org.webcat.core; import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Arrays; import java.util.Enumeration; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.activation.CommandMap; import javax.activation.DataHandler; import javax.activation.FileDataSource; import javax.activation.MailcapCommandMap; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMultipart; import ognl.helperfunction.WOHelperFunctionHTMLTemplateParser; import ognl.helperfunction.WOTagProcessor; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.webcat.archives.ArchiveManager; import org.webcat.archives.IArchiveHandler; import org.webcat.core.git.GitUtilities; import org.webcat.core.git.http.GitRequestHandler; import org.webcat.core.messaging.ApplicationStartupMessage; import org.webcat.core.messaging.FallbackMessageDispatcher; import org.webcat.core.messaging.IMessageDispatcher; import org.webcat.core.messaging.UnexpectedExceptionMessage; import org.webcat.core.webapi.WebAPIRequestHandler; import org.webcat.core.webdav.WebDAVRequestHandler; import org.webcat.dbupdate.UpdateEngine; import org.webcat.woextensions.AjaxUpdateContainerTagProcessor; import org.webcat.woextensions.ECAction; import org.webcat.woextensions.WCContext; import org.webcat.woextensions.WCEC; import org.webcat.woextensions.WCResourceManager; import com.webobjects.appserver.WOComponent; import com.webobjects.appserver.WOContext; import com.webobjects.appserver.WOMessage; import com.webobjects.appserver.WOPageNotFoundException; import com.webobjects.appserver.WORedirect; import com.webobjects.appserver.WORequest; import com.webobjects.appserver.WOResourceManager; import com.webobjects.appserver.WOResponse; import com.webobjects.appserver.WOSession; import com.webobjects.appserver.WOSessionStore; import com.webobjects.eoaccess.EODatabaseChannel; import com.webobjects.eoaccess.EODatabaseContext; import com.webobjects.eoaccess.EOModelGroup; import com.webobjects.eocontrol.EOEditingContext; import com.webobjects.eocontrol.EOEnterpriseObject; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSBundle; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSForwardException; import com.webobjects.foundation.NSLog; import com.webobjects.foundation.NSMutableDictionary; import com.webobjects.foundation.NSNotification; import com.webobjects.foundation.NSNotificationCenter; import com.webobjects.foundation.NSSelector; import com.webobjects.foundation.NSTimestamp; import com.webobjects.woextensions.WOExceptionParser; import com.webobjects.woextensions.WOParsedErrorLine; import er.extensions.ERXExtensions; import er.extensions.appserver.ERXMessageEncoding; import er.extensions.formatters.ERXTimestampFormatter; import er.extensions.foundation.ERXProperties; import er.extensions.foundation.ERXSystem; import er.extensions.foundation.ERXValueUtilities; // ------------------------------------------------------------------------- /** * This is the main Web-CAT application class. It takes care of all loading * and setup issues. It also serves as a central source for accessing * properties and subsystem objects, and provides for centralized handling * of exception handling for the Web-CAT application. * * @author Stephen Edwards * @author Last changed by $Author: aallowat $ * @version $Revision: 1.22 $, $Date: 2012/06/22 16:23:17 $ */ public class Application extends er.extensions.appserver.ERXApplication { //~ Constructors .......................................................... // ---------------------------------------------------------- /** * Creates a new Application object. Also does the following: * <ul> * <li> Sets the <code>EOModelGroup</code> delegate so that * EOModels stored in jarred subsystems can be loaded. </li> * <li> Registers a callback with the notification center for * creating new database channels. </li> * <li> Clears out any stale <code>WCLoginSession</code> objects * remaining in the database. </li> * </ul> */ @SuppressWarnings({ "unchecked", "deprecation" }) public Application() { super(); // Set UTF-8 encoding, to support localization WOMessage.setDefaultEncoding("UTF-8"); WOMessage.setDefaultURLEncoding("UTF-8"); ERXMessageEncoding.setDefaultEncoding("UTF-8"); ERXMessageEncoding.setDefaultEncodingForAllLanguages("UTF-8"); // We'll use plain WO sessions, even in a servlet context, since // restoring sessions through the WCServletSessionStore doesn't // really work. setSessionStoreClassName("WOServerSessionStore"); // I'm not sure these do anything, since the corresponding // notifications should have occurred before this constructor // ever executes :-(. I put them in to replicate features in // ERXApplication.main(), which isn't executed for servlets, but // that is broken. NSNotificationCenter.defaultCenter().addObserver( this, new NSSelector("logNotification", new Class[] { NSNotification.class }), NSBundle.BundleDidLoadNotification, null); NSNotificationCenter.defaultCenter().addObserver( this, new NSSelector("logNotification", new Class[] { NSNotification.class }), NSBundle.LoadedClassesNotification, null); if (log.isInfoEnabled()) { log.info("Web-CAT v" + version() + "\nCopyright (C) 2006-2011 Virginia Tech\n\n" + "Web-CAT comes with ABSOLUTELY NO WARRANTY; this is " + "free software\n" + "under the terms of the GNU Affero General Public License " + "v3. See:\n" + "http://www.gnu.org/licenses/agpl.html\n" + "For full source code, see:\n" + "http://www.sourceforge.net/projects/web-cat\n"); } if (log.isDebugEnabled()) { log.debug("Properties loaded from:"); NSArray<?> dirs = ERXProperties.pathsForUserAndBundleProperties(); for (Object dir : dirs) { log.info("\t" + dir); } dirs = ERXProperties.optionalConfigurationFiles(); if (dirs == null) { log.debug("no optional configuration files specified."); } else { log.debug("Also loading properties from optional files:"); for (Object dir : dirs) { log.info("\t" + dir); } } try { File here = new File("."); log.debug("current dir = " + here.getCanonicalPath()); } catch (java.io.IOException e) { log.error("exception checking cwd: ", e); } } // Present the first page via the default DirectAction. setDefaultRequestHandler( requestHandlerForKey(directActionRequestHandlerKey())); // Register the Git request handler. apiHandler = new WebAPIRequestHandler(); registerRequestHandler(apiHandler, WebAPIRequestHandler.REQUEST_HANDLER_KEY); // Register the entity resource request handler. registerRequestHandler(new EntityResourceRequestHandler(), EntityResourceRequestHandler.REQUEST_HANDLER_KEY); // Register the Git request handler. registerRequestHandler(new GitRequestHandler(), GitRequestHandler.REQUEST_HANDLER_KEY); // Register the WebDAV request handler. registerRequestHandler(new WebDAVRequestHandler(), WebDAVRequestHandler.REQUEST_HANDLER_KEY); // Set page cache size from property setPageCacheSize(ERXValueUtilities.intValueWithDefault( configurationProperties().valueForKey("WOPageCacheSize"), 30)); WCEC.installWOECFactory(); if (!isDevelopmentModeSafe() && isDirectConnectEnabled()) { setDirectConnectEnabled(false); } if (configurationProperties().hasUsableConfiguration()) { log.debug("initializing application"); initializeApplication(); setNeedsInstallation(false); notifyAdminsOfStartup(); } else { updateStaticHtmlResources(); } if (log.isDebugEnabled()) { log.debug("classpath = " + System.getProperty("java.class.path")); } } //~ Methods ............................................................... // ---------------------------------------------------------- /** * The main startup method for the application. * * @param argv The command line arguments */ public static void main(String argv[]) { er.extensions.appserver.ERXApplication.main(argv, Application.class); } // ---------------------------------------------------------- /** * Pause the calling thread until the constructor for the singleton * instance of this class has been created. */ public static void waitForConstructorToComplete() { // Just ensure the instance exists, so we can ignore the result wcApplication().needsInstallation(); } // ---------------------------------------------------------- /** * Pause the calling thread until the application has been completely * initialized (i.e., after the constructor and initializeApplication() * have both completed). */ public static void waitForInitializationToComplete() { int count = 0; boolean logEntryAndExit = !initializationComplete; if (logEntryAndExit && log.isDebugEnabled()) { log.debug("Thread " + Thread.currentThread().getName() + " waiting for intialization to complete"); } while (!initializationComplete) { try { Thread.sleep(500); } catch (InterruptedException e) { // ignore } count++; if (count % 1000 == 0) { log.error("Client thread " + Thread.currentThread().getName() + " is still waiting for initialization to complete (" + (count/2) + " sec)."); } } if (logEntryAndExit && log.isDebugEnabled()) { log.debug("Thread " + Thread.currentThread().getName() + ": intialization completed"); } } // ---------------------------------------------------------- /** * Returns true if this application needs to run its self-installer, * and false if it is ready to run. * @return true if installation is needed */ public boolean needsInstallation() { return needsInstallation; } // ---------------------------------------------------------- /** * Tell this application whether or not it needs to run its self-installer. * @param value true if installation is needed */ public void setNeedsInstallation(boolean value) { needsInstallation = value; } // ---------------------------------------------------------- /** * If installation has been completed, initialize all subsystems in the * application and prepare it for running. */ public void notifyAdminsOfStartup() { new ApplicationStartupMessage().send(); } // ---------------------------------------------------------- public void installPatches() { super.installPatches(); WCContext.installIntoApplication(this); } // ---------------------------------------------------------- /** * If installation has been completed, initialize all subsystems in the * application and prepare it for running. */ public void initializeApplication() { loadArchiveManagers(); updateStaticHtmlResources(); // Apply any pending database updates for the core UpdateEngine.instance().database().setConnectionInfoFromProperties( configurationProperties()); UpdateEngine.instance().applyNecessaryUpdates( new CoreDatabaseUpdates()); // Set the eo model delegator EOModelGroup.defaultGroup().setDelegate(new SubsystemEOMRedirector()); // register for database channel needed notification NSNotificationCenter.defaultCenter().addObserver( this, new NSSelector<Void>("createAdditionalDatabaseChannel", new Class<?>[] { NSNotification.class }), EODatabaseContext.DatabaseChannelNeededNotification, null); // log.debug("models = " + EOModelGroup.defaultGroup()); // set up the SMTP server to use for sending Emails { // This is just support for legacy properties used by Web-CAT String host = configurationProperties().getProperty("mail.smtp.host"); if (host == null || "".equals(host)) { log.info("attempting to set mail.smtp.host from WOSMTPHost"); configurationProperties().setProperty( "mail.smtp.host", SMTPHost()); configurationProperties().attemptToSave(); configurationProperties().updateToSystemProperties(); } else { setSMTPHost(host); } } log.info("Using SMTP host " + SMTPHost()); log.debug("cmdShell = " + cmdShell()); // add handlers for main MIME types MailcapCommandMap mc = (MailcapCommandMap)CommandMap.getDefaultCommandMap(); mc.addMailcap("text/html;; " + "x-java-content-handler=com.sun.mail.handlers.text_html"); mc.addMailcap("text/xml;; " + "x-java-content-handler=com.sun.mail.handlers.text_xml"); mc.addMailcap("text/plain;; " + "x-java-content-handler=com.sun.mail.handlers.text_plain"); mc.addMailcap("multipart/*;; " + "x-java-content-handler=com.sun.mail.handlers.multipart_mixed"); mc.addMailcap("message/rfc822;; " + "x-java-content-handler=com.sun.mail.handlers.message_rfc822"); CommandMap.setDefaultCommandMap(mc); // Remove all state login session data boolean nsLogDebugEnabled = NSLog.debug.isEnabled(); NSLog.debug.setIsEnabled(false); new ECAction() { public void action() { // First, attempt to force the initial JNDI exception because // the name jdbc is not bound try { LoginSession.allObjects(ec); } catch (Exception e) { // Silently swallow it, then retry on the next line } for (LoginSession session : LoginSession.allObjects(ec)) { session.delete(); } NSTimestamp thirtyDaysAgo = new NSTimestamp() .timestampByAddingGregorianUnits(0, 0, -30, 0, 0, 0); for (UsagePeriod period : UsagePeriod.objectsMatchingQualifier( ec, UsagePeriod.startTime.before(thirtyDaysAgo))) { period.delete(); } ec.saveChanges(); }}.run(); NSLog.debug.setIsEnabled(nsLogDebugEnabled); // Force common objects to be loaded into the shared editing context AuthenticationDomain.refreshAuthDomains(); Language.refreshLanguages(); Theme.refreshThemes(); NSLog.debug.setAllowedDebugLevel(NSLog.DebugLevelInformational); NSLog.allowDebugLoggingForGroups(NSLog.DebugGroupMultithreading); // Add useful tag shortcuts for inline component tags. WOHelperFunctionHTMLTemplateParser.registerTagShortcut( "org.webcat.core.TableRow", "tr"); WOHelperFunctionHTMLTemplateParser.registerTagShortcut( "WOComponentContent", "content"); WOHelperFunctionHTMLTemplateParser.registerTagShortcut( "org.webcat.ui.WCTableString", "tstr"); AjaxUpdateContainerTagProcessor tp = new AjaxUpdateContainerTagProcessor(); WOTagProcessor simpleTp = new WOGenericContainerTagProcessor(); WOHelperFunctionHTMLTemplateParser.registerTagProcessorForElementType( tp, "adiv"); WOHelperFunctionHTMLTemplateParser.registerTagProcessorForElementType( tp, "aspan"); WOHelperFunctionHTMLTemplateParser.registerTagProcessorForElementType( simpleTp, "label"); setIncludeCommentsInResponses(false); initializeMessagingSystem(); // Ensure subsystems are all loaded __subsystemManager = new SubsystemManager(configurationProperties()); // subsystemManager(); startTime = new NSTimestamp(); checkBootstrapVersion(); initializationComplete = true; } // ---------------------------------------------------------- /** * A subclass-specific version of the inherited application() static * method. * @return The singleton instance of this class. */ public static Application wcApplication() { return (Application)application(); } // ---------------------------------------------------------- /** * Access the application's subsystem manager. * * @return The subsystem manager */ public SubsystemManager subsystemManager() { if (__subsystemManager == null) { // Wait until initialization is complete before letting the // caller proceed waitForInitializationToComplete(); } return __subsystemManager; } public void logNotification(NSNotification notification) { log.info("notification posted: " + notification); log.info("notification object: " + notification.object()); } // ---------------------------------------------------------- /** * This notification callback is registered to respond when a * new database channel is needed. * * @param notification The notification received */ public void createAdditionalDatabaseChannel(NSNotification notification) { EODatabaseContext dbContext = (EODatabaseContext)notification.object(); EODatabaseChannel dbChannel = new EODatabaseChannel(dbContext); if (dbContext != null) { // consensus is that if you need more than 5 open channels, // you might want to re-think something in your code or model if (dbContext.registeredChannels().count() < 5) { log.debug("createAdditionalDatabaseChannel()"); dbContext.registerChannel(dbChannel); } else { log.error("requesting > 5 database channels"); } } } // ---------------------------------------------------------- /** * Returns the class that represents the sessions that WebObjects should * create. Without this override, WebObjects assumes that the session class * is named "Session" in the same package as the application (which may be * the legacy net.sf.webcat.core.Application class in some cases). * * @return the class for the Session objects */ protected Class<?> _sessionClass() { return Session.class; } // ---------------------------------------------------------- /** * Restores a given session in context. * * @param sessionID The ID of the session to restore * @param context The context for the retrieval * @return The session object */ public WOSession restoreSessionWithID( String sessionID, WOContext context) { WOSession result = super.restoreSessionWithID(sessionID, context); return result; } // ---------------------------------------------------------- /** * Redirect to the login page. * @param context the context of the request * @return The login page */ public WORedirect gotoLoginPage(WOContext context) { WORedirect redirect = (WORedirect)pageWithName("WORedirect", context); String dest = configurationProperties().getProperty("login.url"); if (dest == null) { dest = configurationProperties().getProperty("base.url"); } if (dest == null) { dest = completeURLWithRequestHandlerKey( context, null, null, null, false, 0); } log.debug("gotoLoginPage: " + dest); redirect.setUrl(dest); return redirect; } // ---------------------------------------------------------- /** * Returns the URL for the direct action to view the results. * @param context the context of the request * @param requestHandlerKey * @param aRequestHandlerPath * @param aQueryString * @param isSecure * @param somePort * @return the URL as a string */ public static String completeURLWithRequestHandlerKey( WOContext context, String requestHandlerKey, String aRequestHandlerPath, String aQueryString, boolean isSecure, int somePort) { return completeURLWithRequestHandlerKey( context, requestHandlerKey, aRequestHandlerPath, aQueryString, isSecure, somePort, false); } // ---------------------------------------------------------- /** * Returns the URL for the direct action to view the results. * @param context the context of the request * @param requestHandlerKey * @param aRequestHandlerPath * @param aQueryString * @param isSecure * @param somePort * @param forceSecureSetting * @return the URL as a string */ public static String completeURLWithRequestHandlerKey( WOContext context, String requestHandlerKey, String aRequestHandlerPath, String aQueryString, boolean isSecure, int somePort, boolean forceSecureSetting) { WORequest request = context.request(); String dest = context.completeURLWithRequestHandlerKey( requestHandlerKey, aRequestHandlerPath, aQueryString, isSecure, somePort); log.debug("prior to munging, dest = " + dest); if (urlHostPrefix == null) { String result = configurationProperties().getProperty("base.url"); if (result != null) { Matcher matcher = Pattern.compile("^http(s)?://[^/]*", Pattern.CASE_INSENSITIVE ).matcher(result); if (matcher.find()) { urlHostPrefix = matcher.group() + "/"; } } if (urlHostPrefix == null && request != null) { urlHostPrefix = "http://" + hostName(request) + "/"; } log.debug("urlHostPrefix = " + urlHostPrefix); } if (urlHostPrefix != null) { dest = dest.replaceFirst("^http(s)?://[^/]*(/)?", urlHostPrefix); } dest = dest.replaceFirst("^http(s)?:", "http" + (((forceSecureSetting && isSecure) || (!forceSecureSetting && request != null && isSecure(request))) ? "s" : "") + ":"); log.debug("link = " + dest); return dest; } // ---------------------------------------------------------- /** * Returns <code>true</code> if the server prefers SSL connections. * @return <code>true</code> if SSL should be used by default. */ static public boolean useSecureConnectionsByDefault() { if (defaultsToSecure == null) { String base = configurationProperties().getProperty("base.url"); if (base != null) { defaultsToSecure = Boolean.valueOf( base.startsWith("https") || base.startsWith("HTTPS")); } } return defaultsToSecure == null ? false : defaultsToSecure.booleanValue(); } // ---------------------------------------------------------- /** * Returns <code>true</code> if the request was made via https/SSL, * <code>false</code> otherwise. It makes the rather grand assumption that * all HTTPS connections are on port 443. * @param request the request being processed * @return <code>true</code> if the request was made via https/SSL, * <code>false</code> otherwise. */ static public boolean isSecure(WORequest request) { /** require [valid_param] request != null; **/ // The method of determining whether the request was via HTTPS depends // on the adaptor / the web server. // First we try and see if the request was made on the standard // https port String serverPort = null; for (int i = 0; serverPort == null && i < SERVER_PORT_KEYS.count(); i++) { serverPort = request.headerForKey( SERVER_PORT_KEYS.objectAtIndex(i)); } // Apache and some other web servers use this to indicate HTTPS mode. // This is much better as it does not depend on the port number used. String httpsMode = request.headerForKey("https"); // If either the https header is 'on' or the server port is 443 then // we consider this to be an HTTP request. return (httpsMode != null && httpsMode.equalsIgnoreCase("on")) || (serverPort != null && serverPort.equals("443")) || (httpsMode == null && serverPort == null && useSecureConnectionsByDefault()); } // ---------------------------------------------------------- /** * Returns the host name (a.k.a. server name, domain name) used in * this request. The request headers are examined for the keys in * HOST_NAME_KEYS to determine the name. * * @param request the request to get the hostname from * @return the host name used in this request. */ static public String hostName(WORequest request) { /** require [valid_param] request != null; **/ String hostName = null; for (int i = 0; hostName == null && i < HOST_NAME_KEYS.count(); i++) { hostName = request.headerForKey(HOST_NAME_KEYS.objectAtIndex(i)); } return hostName; /** ensure [valid_result] Result != null; **/ } // ---------------------------------------------------------- /** * If there is an error restoring a session (because of timeouts * or termination) this simply redirects to the login page again. * @param context The context for the retrieval * @return The login page */ public WOResponse handleSessionRestorationErrorInContext( WOContext context) { log.debug("handleSessionRestorationErrorInContext()"); return gotoLoginPage(context).generateResponse(); } // ---------------------------------------------------------- public WOResponse handleSessionCreationErrorInContext( WOContext context) { log.debug("handleSessionCreationErrorInContext()"); return super.handleSessionCreationErrorInContext(context); } // ---------------------------------------------------------- /** * Returns the given component by name. * * @param name The name of the component to find * @param context The context for the retrieval * @return The component */ public WOComponent pageWithName(String name, WOContext context) { if (requestLog.isDebugEnabled()) { requestLog.debug("pageWithName( " + ((name == null) ? "<null>" : name) + ", " + context + " )"); } if (name == null || name.length() == 0 || "Main".equals(name)) { log.info("pageWithName(" + name + ") called for URI " + context.request().uri()); if (context.hasSession() && context.session() instanceof Session && ((Session)context.session()).isLoggedIn()) { name = ((Session)context.session()).tabs.selectDefault() .pageName(); } else { name = LoginPage.class.getName(); } } try { return super.pageWithName(name, context); } catch (WOPageNotFoundException pnf) { log.info("pageWithName(" + name + ")\n\tfor URI " + context.request().uri() + "\n\tproduced " + pnf); return super.pageWithName(LoginPage.class.getName(), context); } } // ---------------------------------------------------------- /** * Force the garbage collector to run and release as much memory * back into the heap's free pool as possible. */ public void forceFullGarbageCollection() { final int iterationLimit = 10; Runtime runtime = Runtime.getRuntime(); log.info("Forcing full garbage collection..."); long isFree = runtime.freeMemory(); long wasFree; int iterations = 0; log.info(" wasFree: " + isFree + "..."); do { wasFree = isFree; runtime.gc(); isFree = runtime.freeMemory(); } while (isFree > wasFree && iterations++ < iterationLimit); runtime.runFinalization(); log.info(" isFree: " + isFree + ", total mem : " + runtime.totalMemory() + "..."); } // ---------------------------------------------------------- /** * Overrides parent implementation to add heap check and force * garbage collection when necessary. */ @Override public WOResponse dispatchRequest(WORequest aRequest) { Runtime runtime = Runtime.getRuntime(); final int freeLimit = 100000; final int dieLimit = 5000; if (runtime.freeMemory() < freeLimit) { forceFullGarbageCollection(); if (runtime.freeMemory() < freeLimit) { if (runtime.freeMemory() < dieLimit) { sendAdminEmail("Dying, out of memory...", "Cannot force GC to free more than " + freeLimit + " bytes. Terminating."); killInstance(); } else { sendAdminEmail("Running out of memory...", "Cannot force GC to free more than " + freeLimit + " bytes."); } } } if (dieTime != null) { if (dieTime.before(new NSTimestamp())) { killInstance(); } } else if (isRefusingNewSessions()) { dieTime = (new NSTimestamp()) .timestampByAddingGregorianUnits( 0, // years 0, // months 0, // days 0, // hours 5, // minutes 0 // seconds ); } if (requestLog.isDebugEnabled()) { requestLog.debug("dispatchRequest(): method = " + aRequest.method()); requestLog.debug("\tqueryString = " + aRequest.queryString()); requestLog.debug("\trequestHandlerKey = " + aRequest.requestHandlerKey()); requestLog.debug("\trequestHandlerPath = " + aRequest.requestHandlerPath()); requestLog.debug("\turi = " + aRequest.uri()); requestLog.debug("\tcookies = " + aRequest.cookies()); } WOResponse result = super.dispatchRequest(aRequest); if (requestLog.isDebugEnabled()) { requestLog.debug("dispatchRequest() result:\n" + result); } return result; } // ---------------------------------------------------------- /** * Access the time this application was started up. * @return the time when this instance started */ public NSTimestamp startTime() { return startTime; } // ---------------------------------------------------------- /** * Access the application's property settings. * @return the property settings */ public WCConfigurationFile properties() { return configurationProperties(); } // ---------------------------------------------------------- static public String configurationFileName() { String configFileName = ERXSystem.getProperty( "webobjects.user.dir"); if (configFileName == null || configFileName.equals("")) { configFileName = ERXSystem.getProperty("user.dir"); } configFileName = configFileName.replace('\\', '/'); if (configFileName.length() > 0 && !configFileName.endsWith("/")) { configFileName += "/"; } configFileName += "configuration.properties"; return configFileName; } // ---------------------------------------------------------- /** * Access the application's property settings. * @return the property settings */ static public WCConfigurationFile configurationProperties() { if (__properties == null) { __properties = new WCConfigurationFile(configurationFileName()); __properties.updateToSystemProperties(); } return __properties; } // ---------------------------------------------------------- /** * Access the application's identifier (name). * @return the identifier */ static public String appIdentifier() { if (__appIdentifier == null) { String appIdentifierRaw = configurationProperties().getProperty( "coreApplicationIdentifier"); __appIdentifier = (appIdentifierRaw == null) ? "[Web-CAT] " : "[" + appIdentifierRaw + "] "; } return __appIdentifier; } // ---------------------------------------------------------- /** * Overrides default WO version to include XP as a supported * platform. */ public boolean _isForeignSupportedDevelopmentPlatform() { return super._isForeignSupportedDevelopmentPlatform() || isAdditionalForeignSupportedDevelopmentPlatform(); } // ---------------------------------------------------------- /** * Overrides default WO version to force deployment-mode behavior * until valid configuration has been reached. */ public boolean _rapidTurnaroundActiveForAnyProject() { return staticHtmlResourcesNeedInitializing || super._rapidTurnaroundActiveForAnyProject(); } // ---------------------------------------------------------- /** * Check for Windows XP * @return true when running on XP */ protected boolean isAdditionalForeignSupportedDevelopmentPlatform() { String s = System.getProperty("os.name"); return s != null && s.equals("Windows XP"); } // ---------------------------------------------------------- /** * Check to see if we are running on any flavor of Windows. * @return true when running on Windows */ public static boolean isRunningOnWindows() { String s = System.getProperty("os.name"); return s != null && s.indexOf("Windows") >= 0; } // ---------------------------------------------------------- /** * Reports an exception by logging, e-mailing admins, and returning * an error page. * @param exception to be reported * @param extraInfo dictionary of extra information about what was * happening when the exception was thrown * @param context the context in which the exception occurred * @return an error page */ @SuppressWarnings("unchecked") public WOResponse reportException(Exception exception, NSDictionary extraInfo, WOContext context) { Throwable t = exception instanceof NSForwardException ? ((NSForwardException) exception).originalException() : exception; if (t != null && t instanceof java.lang.IllegalStateException && t.getMessage() != null && t.getMessage().contains("Couldn't locate action class")) { // Then don't e-mail this, because it is really just a bad // client-side request with a bad action class name } else { new UnexpectedExceptionMessage(t, context, extraInfo, null) .send(); } if (context != null) { // Return a "clean" error page WOComponent errorPage = pageWithName(ErrorPage.class.getName(), context); errorPage.takeValueForKey(t, "exception"); return errorPage.generateResponse(); } else { // No context, so we cannot generate a real error page. Instead, // return null, which should force a trivial error message return null; } } // ---------------------------------------------------------- /** * Replaces the default exception page in WebObjects, and e-mails * a copy of the message to the administrator notification * address list. * * @param exception the exception that occurred * @param context the context in which the exception occurred * @return the error message page */ public WOResponse handleException(Exception exception, WOContext context) { try { // We first want to test if we ran out of memory. If so we need // to quit ASAP. handlePotentiallyFatalException(exception); // Not a fatal exception, business as usual. NSDictionary<?, ?> extraInfo = extraInformationForExceptionInContext(exception, context); WOResponse response = reportException(exception, extraInfo, context); if (response == null && context != null) { response = super.handleException(exception, context); } return response; } catch (Throwable t) { log.error("handleException failed", t); return super.handleException(exception, context); } } // ---------------------------------------------------------- /** * Replaces the default page restoration error page in WebObjects * with {@link WCPageRestorationErrorPage}. * * @param context the context in which the exception occurred * @return the error message page */ public WOResponse handlePageRestorationErrorInContext(WOContext context) { return pageWithName( WCPageRestorationErrorPage.class.getName(), context) .generateResponse(); } // ---------------------------------------------------------- public NSMutableDictionary<?, ?> extraInformationForExceptionInContext( Exception exception, WOContext context) { NSMutableDictionary<?, ?> result = super.extraInformationForExceptionInContext(exception, context); if ( context != null && context.hasSession() && context.session() instanceof Session) { Session s = (Session)context.session(); TabDescriptor currentTab = (s.tabs == null) ? null : s.tabs.selectedDescendant(); result.setObjectForKey( (currentTab == null) ? "null" : currentTab.printableTabLocation(), "current tab"); } return result; } // ---------------------------------------------------------- /** * Sends a text e-mail message to the specified recipient. * * @param to the destination address * @param subject the subject line * @param body the body of the message */ static public void sendSimpleEmail(String to, String subject, String body) { sendSimpleEmail(new NSArray<String>(to), subject, body, null); } // ---------------------------------------------------------- /** * Sends a text e-mail message to the specified recipient. * * @param to the destination address * @param subject the subject line * @param body the body of the message * @param attachments the attachments */ static public void sendSimpleEmail( String to, String subject, String body, List<File> attachments) { sendSimpleEmail(new NSArray<String>(to), subject, body, attachments); } // ---------------------------------------------------------- /** * Sends a text e-mail message to the specified recipients. * * @param to the destination addresses * @param subject the subject line * @param body the body of the message * @param attachments the attachments */ static public void sendSimpleEmail(NSArray<String> to, String subject, String body) { sendSimpleEmail(to, subject, body, null); } // ---------------------------------------------------------- /** * Sends a text e-mail message to the specified recipients. * * @param to the destination addresses * @param subject the subject line * @param body the body of the message * @param attachments the attachments */ static public void sendSimpleEmail(NSArray<String> to, String subject, String body, List<File> attachments) { try { // Define message javax.mail.Message message = new javax.mail.internet.MimeMessage( javax.mail.Session.getInstance( configurationProperties(), null)); message.setFrom(new InternetAddress( configurationProperties().getProperty("coreAdminEmail"))); message.setSentDate(new NSTimestamp()); // Add each recipient to the message. for (String toAddress : to) { String defaultDomain = configurationProperties() .getProperty("mail.default.domain"); if (toAddress.indexOf( '@' ) == -1 && defaultDomain != null) { toAddress += "@" + defaultDomain; } message.addRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress(toAddress)); } message.setSubject(appIdentifier() + subject); if (attachments == null || attachments.size() == 0) { message.setText(body); } else { // Create the message part javax.mail.BodyPart messageBodyPart = new MimeBodyPart(); // Fill the message messageBodyPart.setText(body); // Create a Multipart javax.mail.Multipart multipart = new MimeMultipart(); // Add part one multipart.addBodyPart(messageBodyPart); // // The next parts are attachments // for (File file : attachments) { // Create another body part messageBodyPart = new MimeBodyPart(); // Don't include files bigger than this as e-mail // attachments if (file.length() < maxAttachmentSize) { // Get the attachment FileDataSource source = new FileDataSource(file); // Set the data handler to the attachment messageBodyPart.setDataHandler( new DataHandler(source)); // Set the filename messageBodyPart.setFileName(file.getName()); } else { // Fill the message messageBodyPart.setText( "File " + file.getName() + " has been omitted from this message (" + file.length() + " bytes)\n"); } // Add attachment multipart.addBodyPart(messageBodyPart); } // Put parts in message message.setContent(multipart); } // Send the message if ("donotsendmail".equals( configurationProperties().getProperty("mail.smtp.host"))) { if (log.isDebugEnabled()) { log.debug("E-MAIL DISABLED: unsent message:\nTo: " + to + "\nSubject: " + (subject == null ? "null" : subject) + "\nBody:\n" + (body == null ? "null" : body)); } } else { // AJA 2010.01.12: Fix for the JAF/Mail issue that prevented // mail from being sent from a servlet when running under // Java 1.6: // http://blog.hpxn.net/2009/12/02/tomcat-java-6-and-javamail-cant-load-dch/ ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader( Application.class.getClassLoader()); javax.mail.Transport.send(message); } finally { Thread.currentThread().setContextClassLoader( originalClassLoader); } } } catch (Exception e) { String msg = e.getMessage(); if (msg != null && msg.contains("java.net.UnknownHostException:")) { log.error("Exception sending mail message: " + e); } else { log.error("Exception sending mail message:\n", e); log.error("unsent message:\nTo: " + (to == null ? "null" : to) + "\nSubject: " + (subject == null ? "null" : subject) + "\nBody:\n" + (body == null ? "null" : body)); } } } // ---------------------------------------------------------- /** * Sends a text e-mail message to the system administrators. * * @param subject the subject line * @param body the body of the message * @return the array of admin e-mail addresses */ public static NSArray<String> adminEmailAddresses() { String adminList = configurationProperties().getProperty("adminNotifyAddrs"); if (adminList == null) { adminList = configurationProperties() .getProperty("coreAdminEmail"); } else { String primaryAdmin = configurationProperties() .getProperty("coreAdminEmail"); if (primaryAdmin != null && !adminList.contains(primaryAdmin)) { adminList = primaryAdmin + "," + adminList; } } if (adminList == null) { return null; } String[] admins = adminList.split("\\s*,\\s*"); return new NSArray<String>(admins); } // ---------------------------------------------------------- /** * Sends a text e-mail message to the system administrators. * * @param subject the subject line * @param body the body of the message */ public static void sendAdminEmail(String subject, String body) { sendAdminEmail(subject, body, null); } // ---------------------------------------------------------- /** * Sends a text e-mail message to the system administrators. * * @param subject the subject line * @param body the body of the message * @param attachments the attachments */ public static void sendAdminEmail( String subject, String body, List<File> attachments) { NSArray<String> adminList = adminEmailAddresses(); if (adminList == null || adminList.count() == 0) { log.error("No bound admin e-mail addresses. " + "Cannot send message:\n" + "Subject: " + subject + "\n" + "Message:\n" + body); return; } sendSimpleEmail(adminList, subject, body, attachments); } // ---------------------------------------------------------- /** * Generate a string of extra info for the given context (can be used * for debugging output). * @param context the context for this request (can be null, if desired) * @return the extra information in a formatted, human-readable, * multi-line string */ public static String extraInfoForContext(WOContext context) { if (context != null && context.page() != null) { StringBuffer buffer = new StringBuffer(); buffer.append("CurrentPage = "); buffer.append(context.page().name()); buffer.append("\n"); if (context.component() != null) { buffer.append("CurrentComponent = "); buffer.append(context.component().name()); buffer.append("\n"); if (context.component().parent() != null) { WOComponent component = context.component(); while (component.parent() != null) { component = component.parent(); buffer.append(" parent = "); buffer.append(component.name()); buffer.append("\n"); } } } buffer.append("uri = "); buffer.append(context.request().uri()); buffer.append("\n"); if (context.hasSession()) { if (context.session().statistics() != null) { buffer.append("PreviousPageList = "); buffer.append(context.session().statistics()); buffer.append("\n"); } if (context.session() instanceof Session) { Session s = (Session)context.session(); TabDescriptor currentTab = (s.tabs == null) ? null : s.tabs.selectedDescendant(); buffer.append("current tab = "); buffer.append((currentTab == null) ? "null" : currentTab.printableTabLocation()); buffer.append("\n"); } } return buffer.toString(); } else { return null; } } // ---------------------------------------------------------- /** * Gets a value indicating whether the application is running as a * servlet. * * @return True if the application is running as a servlet, otherwise * false. */ public static boolean isRunningAsServlet() { return net.sf.webcat.WCServletAdaptor.getInstance() != null; } // ---------------------------------------------------------- /** * Method to assemble information on the exception, including the * message, stack trace, the name of the component which contained * the error (or caused it), and any session information required. * This is an instance method instead of being static to ensure that * it is synchronized. * @param anException the exception that occurred * @param extraInfo dictionary of extra information about what was * happening when the exception was thrown * @param aContext the context in which the exception occurred * @return a printable description of the error */ public synchronized String informationForExceptionInContext( Throwable anException, NSDictionary<?, ?> extraInfo, WOContext aContext) { Session s = (aContext != null && aContext.hasSession()) ? (Session)aContext.session() : null; // Set up a buffer for the content StringBuffer errorBuffer = new StringBuffer(); if (s != null && s.primeUser() != null) { // Get the pid of the user of the session errorBuffer.append("User : "); errorBuffer.append(s.primeUser().nameAndUid()); } // Get the date and time for the exception NSTimestamp now = new NSTimestamp(); errorBuffer.append("\nDate/time: "); errorBuffer.append(now.toString()); if (aContext != null && aContext.request() != null) { errorBuffer.append("\nRequest: "); errorBuffer.append(aContext.request().uri()); errorBuffer.append("\nReferer: "); errorBuffer.append(aContext.request().headerForKey("referer")); } if (errorLoggingContext == null) { errorLoggingContext = WCEC.newEditingContext(); } try { errorLoggingContext.lock(); LoggedError loggedError = null; if (!needsInstallation()) { loggedError = LoggedError.objectForException( errorLoggingContext, anException); } if (loggedError != null) { loggedError.setOccurrences(loggedError.occurrences() + 1); loggedError.setMostRecent(now); errorBuffer.append("\nOccurrences: "); errorBuffer.append(loggedError.occurrences()); } if ( aContext != null && aContext.component() != null) { // Get the current component errorBuffer.append("\nComponent: "); String name = aContext.component().name(); if (name == null) { name = aContext.component().getClass().getName(); } if (loggedError != null) { loggedError.setComponent(name); } errorBuffer.append(name); } if ( aContext != null && aContext.page() != null && loggedError != null) { loggedError.setPage(aContext.page().name()); } // Get the session associated (if any) if (s != null) { errorBuffer.append("\nSessionID: " + s.sessionID()); } if (anException != null) { // Get the full message for the exception errorBuffer.append("\n\nException:\n----------\n"); errorBuffer.append(anException.getClass().getName()); errorBuffer.append(":\n"); errorBuffer.append(anException.getMessage()); if (anException.getMessage() != null && loggedError != null) { loggedError.setMessage(anException.getMessage()); } if (extraInfo != null) { errorBuffer.append( "\n\nExtra information:\n--------------------\n"); for (Enumeration<?> e = extraInfo.keyEnumerator(); e.hasMoreElements();) { Object key = e.nextElement(); if (!"Session".equals(key) && !"Bundles".equals(key)) { Object value = extraInfo.objectForKey(key); errorBuffer.append(key); errorBuffer.append(" = "); errorBuffer.append(value); errorBuffer.append('\n'); } } } // Get the stack trace for the exception errorBuffer.append("\nStack trace:\n-----------------\n"); if (loggedError != null) { StringWriter writer = new StringWriter(); PrintWriter pwriter = new PrintWriter(writer); anException.printStackTrace(pwriter); pwriter.close(); loggedError.setStackTrace( writer.getBuffer().toString()); } if (!isRunningAsServlet()) { // If we're not running as a servlet, then assume we're // in a developer environment and generate fully // compliant stack trace info for IDE parsing: StringWriter writer = new StringWriter(); PrintWriter pwriter = new PrintWriter(writer); anException.printStackTrace(pwriter); pwriter.close(); errorBuffer.append(writer.getBuffer()); } else { // For deployment, use a simplified stack trace // presentation to make e-mail messages lighter (and // also somewhat more readable). WOExceptionParser exParser = new WOExceptionParser(anException); Enumeration<?> traceEnum = exParser.stackTrace().objectEnumerator(); // Append each trace line while (traceEnum.hasMoreElements()) { WOParsedErrorLine aLine = (WOParsedErrorLine)traceEnum.nextElement(); errorBuffer.append("at " + aLine.methodName() + "(" + aLine.fileName() + ":" + aLine.lineNumber() + ")\n"); } } } else { if (extraInfo != null) { errorBuffer.append( "\n\nExtra information:\n--------------------\n"); for (Enumeration<?> e = extraInfo.keyEnumerator(); e.hasMoreElements();) { Object key = e.nextElement(); Object value = extraInfo.objectForKey(key); errorBuffer.append(key); errorBuffer.append("\t= "); errorBuffer.append(value); errorBuffer.append('\n'); } } } errorLoggingContext.saveChanges(); } catch (Exception e) { EOEditingContext old = errorLoggingContext; errorLoggingContext = null; try { try { // Try to unlock it, if possible old.unlock(); } catch (Exception ee) { // ignore, since we're throwing it away } old.dispose(); } catch (Exception e2) { log.fatal("error releasing error logging editing context", e2); log.fatal("original exception causing error logging " + "context to be released", e); } } finally { if (errorLoggingContext != null) { errorLoggingContext.unlock(); } } // Return the information return errorBuffer.toString(); } // ---------------------------------------------------------- @SuppressWarnings("deprecation") public void refuseNewSessions(boolean refuse) { boolean isDirectConnectEnabled = isDirectConnectEnabled(); if (refuse) { setDirectConnectEnabled(false); int timeToKill = configurationProperties().intForKey( "ERTimeToKill"); if (timeToKill > 0) { dieTime = (new NSTimestamp()) .timestampByAddingGregorianUnits( 0, 0, 0, 0, 0, timeToKill); } } else { dieTime = null; } super.refuseNewSessions(refuse); setDirectConnectEnabled(isDirectConnectEnabled); } // ---------------------------------------------------------- public NSTimestamp deathTime() { return dieTime; } // ---------------------------------------------------------- public boolean deathIsScheduled() { return dieTime != null; } // ---------------------------------------------------------- @SuppressWarnings("deprecation") public String deathMessage() { if (dieTime != null && deathMessage == null) { StringBuffer buffer = new StringBuffer(200); buffer.append("<b>Immediate shutdown:</b> "); buffer.append("Web-CAT will be going off-line at "); ERXTimestampFormatter formatter = new ERXTimestampFormatter("%I:%M%p"); java.text.FieldPosition pos = new java.text.FieldPosition(0); formatter.format(dieTime, buffer, pos); buffer.append(". Save your work and logout."); deathMessage = buffer.toString(); } return deathMessage; } // ---------------------------------------------------------- public static String cmdShell() { if (cmdShell == null) { cmdShell = configurationProperties().getProperty("cmdShell"); if (cmdShell == null) { if (isRunningOnWindows()) { cmdShell = "cmd /c"; } else { cmdShell = "sh -c \""; } } int len = cmdShell.length(); if (len > 0 && cmdShell.charAt( len - 1 ) != ' ' && cmdShell.charAt( len - 1 ) != '"') { cmdShell += " "; } } return cmdShell; } // ---------------------------------------------------------- /* (non-Javadoc) * @see er.extensions.appserver.ERXApplication#killInstance() */ public void killInstance() { String killAction = configurationProperties().getProperty("coreKillAction"); if (killAction == null) { log.fatal("Using default kill action", new Exception("from here")); super.killInstance(); } else { String cmd = cmdShell() + killAction; log.fatal("Killing application using: " + cmd, new Exception("from here")); Process proc = null; try { proc = Runtime.getRuntime().exec(cmd); proc.waitFor(); // wait for ten seconds to give the kill command time to // work externally, since immediate return of the process // may not always mean its work is complete Thread.sleep(10000); } catch (Exception e) { // stopped by timeout if (proc != null) { proc.destroy(); } log.fatal("exception executing kill action '" + cmd + "'", e); super.killInstance(); } } } // ---------------------------------------------------------- @Override public WOSessionStore sessionStore() { WOSessionStore store = super.sessionStore(); if (log.isDebugEnabled()) { log.debug("sessionStore() class = " + store.getClass().getName()); log.debug("sessionStore() = " + store); } return store; } // ---------------------------------------------------------- public String version() { if (version == null) { WCConfigurationFile config = properties(); int major = 0; int minor = 0; int revision = 0; int date = 0; for (Enumeration<Object> keys = System.getProperties().keys(); keys.hasMoreElements();) { String key = keys.nextElement().toString(); if (key.endsWith("version.major")) { int thisMajor = config.intForKey(key); if (thisMajor > major) { major = thisMajor; } } else if (key.endsWith( "version.minor")) { int thisMinor = config.intForKey(key); if (thisMinor > minor) { minor = thisMinor; } } else if ( key.endsWith("version.revision")) { int thisRevision = config.intForKey(key); if (thisRevision > revision) { revision = thisRevision; } } else if (key.endsWith("version.date")) { int thisDate = config.intForKey(key); if (thisDate > date) { date = thisDate; } } } version = "" + config.intForKey("webcat.version.major") + "." + config.intForKey("webcat.version.minor") + "." + config.intForKey("webcat.version.revision") + "/" + major + "." + minor + "." + revision + "." + date; } return version; } // ---------------------------------------------------------- public String sessionStoreClassName() { String result = super.sessionStoreClassName(); if (log.isDebugEnabled()) { log.debug("sessionStoreClassName() = " + result); } return result; } // ---------------------------------------------------------- public WOResourceManager createResourceManager() { return new WCResourceManager(); } // ---------------------------------------------------------- /** * Execute an external command, using the appropriate command shell * setting from the app configuration data. If subsystems have * already been initialized, their exported ENV settings will be passed * to the command as well. * @param commandLine The command to execute * @param cwd The (optional) working directory in which the command * should be executed. If null, the application's cwd will be used. * @throws java.io.IOException if an error occurs communicating with * the child process * @throws InterruptedException if the child process gets interrupted */ public void executeExternalCommand(String commandLine, File cwd) throws java.io.IOException, InterruptedException { Process proc = null; try { proc = executeExternalCommandAsync(commandLine, cwd); int exitCode = proc.waitFor(); log.debug("external command returned exit code: " + exitCode); } catch (InterruptedException e) { // stopped by timeout if (proc != null) { proc.destroy(); } throw e; } } // ---------------------------------------------------------- /** * <p> * Execute an external command, using the appropriate command shell * setting from the app configuration data, but does not wait for it to * finish. The Java Process object will be returned so that it can be * manipulated and its input/output streams can be accessed. * </p><p> * If subsystems have already been initialized, their exported ENV settings * will be passed to the command as well. * </p> * * @param commandLine The command to execute * @param cwd The (optional) working directory in which the command * should be executed. If null, the application's cwd will be used. * @return the Java Process object that represents the external process * @throws java.io.IOException if an error occurs communicating with * the child process * @throws InterruptedException if the child process gets interrupted */ public Process executeExternalCommandAsync(String commandLine, File cwd) throws java.io.IOException { String[] cmdArray = null; Process proc = null; // Tack on the command shell prefix to the beginning, quoting the // whole argument sequence if necessary { String shell = org.webcat.core.Application.cmdShell(); if (shell != null && shell.length() > 0) { if (shell.charAt(shell.length() - 1) == '"') { cmdArray = shell.split("\\s+"); cmdArray[cmdArray.length - 1] = commandLine; } else { commandLine = shell + commandLine; } } } String[] envp = null; // If subsystems are already loaded, get their ENV info: if (__subsystemManager != null) { envp = subsystemManager().envp(); } if (cmdArray != null) { log.debug("executeExternalCommand(): " + Arrays.toString(cmdArray)); proc = Runtime.getRuntime().exec(cmdArray, envp, cwd); } else { log.debug("executeExternalCommand(): " + commandLine); proc = Runtime.getRuntime().exec(commandLine, envp, cwd); } return proc; } // ---------------------------------------------------------- /** * Enables logging of executed SQL statements. */ public static synchronized void enableSQLLogging() { Logger.getLogger("NSLog").setLevel(Level.DEBUG); Logger.getLogger(er.extensions.logging.ERXNSLogLog4jBridge.class) .setLevel(Level.DEBUG); Logger.getLogger("com.webobjects.jdbcadaptor.MySqlPlugin") .setLevel(Level.DEBUG); Logger.getLogger("er.transaction.adaptor.EOAdaptorDebugEnabled") .setLevel(Level.DEBUG); NSLog.allowDebugLoggingForGroups(NSLog.DebugGroupDatabaseAccess); System.setProperty("er.extensions.ERXAdaptorChannelDelegate.enabled", "true"); } // ---------------------------------------------------------- /** * Disables logging of executed SQL statements. */ public static synchronized void disableSQLLogging() { Logger.getLogger("NSLog").setLevel(Level.OFF); Logger.getLogger(er.extensions.logging.ERXNSLogLog4jBridge.class) .setLevel(Level.OFF); Logger.getLogger("com.webobjects.jdbcadaptor.MySqlPlugin") .setLevel(Level.OFF); Logger.getLogger("er.transaction.adaptor.EOAdaptorDebugEnabled") .setLevel(Level.OFF); NSLog.refuseDebugLoggingForGroups(NSLog.DebugGroupDatabaseAccess); System.setProperty("er.extensions.ERXAdaptorChannelDelegate.enabled", "false"); } // ---------------------------------------------------------- private void updateStaticHtmlResources() { String staticHtmlBase = configurationProperties().getProperty( "static.html.baseURL"); if (isRunningAsServlet()) { File woaDir = configurationProperties().file().getParentFile(); File appBase = woaDir.getParentFile().getParentFile(); File staticResourceBaseDir = appBase; File frameworkDir = new File(woaDir, "Contents/Frameworks/Library/Frameworks"); if (!frameworkDir.exists()) { frameworkDir = new File(woaDir, "Contents/Library/Frameworks"); } String staticHtmlDirName = configurationProperties() .getProperty("static.html.dir"); String lastStaticHtmlDirName = configurationProperties() .getProperty("last.static.html.dir"); if (staticHtmlDirName != null && staticHtmlBase != null) { // Only use the static HTML dir parameter if the app is also // configured to use an external static HTML base URL for // resources. staticHtmlDirName = staticHtmlDirName.replace('\\', '/'); File dir = new File(staticHtmlDirName); if (!dir.exists()) { try { dir.mkdirs(); } catch (Exception e) { log.error("Exception attempting to create static HTML " + "resource dir '" + staticHtmlDirName + "':", e); } } if (dir.exists()) { staticResourceBaseDir = dir; } } else { // If there is no static HTML dir specified, or if there is // no base URL given, then the servlet will have to serve all // these resources, and therefore we need to store static // resources in the app base dir. // But we still need a value we can use to compare against the // previous value, to see if the location has changed staticHtmlDirName = appBase.getAbsolutePath() .replace('\\', '/'); } if (log.isDebugEnabled()) { log.debug("updateStaticHtmlResources: staticHtmlDir = " + staticHtmlDirName); log.debug("updateStaticHtmlResources: lastStaticHtmlDir = " + lastStaticHtmlDirName); log.debug("appBase = " + appBase.getAbsolutePath()); log.debug("staticResourceBase = " + staticResourceBaseDir); } // Note: we can't use the subsystem manager yet, since the // application has not been fully initialized yet and that data // isn't available at this point. for (File framework : frameworkDir.listFiles()) { log.debug("Checking for static html resources in => " + framework.getName()); String frameworkName = framework.getName(); if (!frameworkName.endsWith(".framework")) { continue; } frameworkName = frameworkName.substring(0, frameworkName.length() - ".framework".length()); String frameworkDatestamp = configurationProperties(). getProperty(frameworkName + ".version.date"); // frameworkLastUpdated will be null for frameworks that are // not packaged subsystems if (frameworkDatestamp != null) { String lastUpdated = configurationProperties().getProperty( "static.HTML.date." + frameworkName, "00000000"); if (log.isDebugEnabled()) { log.debug("Comparing last update " + lastUpdated + " against " + frameworkName + " datestamp of: " + frameworkDatestamp); } if (!lastUpdated.equals(frameworkDatestamp)) { log.debug("Updating resources for " + frameworkName); updateExecutablePermissionsForFramework( frameworkName, framework); updateStaticHtmlResourcesForFramework( framework, staticResourceBaseDir); // Now check the additional included frameworks too String alsoContainsList = configurationProperties() .getProperty(frameworkName + ".alsoContains"); if (alsoContainsList != null) { for (String fw : alsoContainsList.split(",\\s*")) { File otherFramework = new File(frameworkDir, fw); if (log.isDebugEnabled()) { String includedName = fw.substring(0, fw.length() - ".framework".length()); log.debug("Updating resources for " + includedName); } updateStaticHtmlResourcesForFramework( otherFramework, staticResourceBaseDir); } } configurationProperties().setProperty( "static.HTML.date." + frameworkName, frameworkDatestamp); } else { log.debug("Already up to date: " + frameworkName); } } } // Attempt to update the "last saved" info configurationProperties().setProperty("last.static.html.dir", staticResourceBaseDir.getAbsolutePath().replace('\\', '/')); configurationProperties().attemptToSave(); if (staticHtmlBase == null) { staticHtmlBase = configurationProperties().getProperty( "base.url"); if (staticHtmlBase == null) { staticHtmlBase = Application.application().servletConnectURL(); System.out.println("servlet URL = " + staticHtmlBase); staticHtmlBase = staticHtmlBase.replaceFirst("^[^/]*//[^/]+/", ""); System.out.println("servlet URL = " + staticHtmlBase); } if (staticHtmlBase.endsWith(".woa")) { int loc = staticHtmlBase.lastIndexOf('/'); if (loc > 0) { staticHtmlBase = staticHtmlBase.substring(0, loc); } } if (staticHtmlBase.endsWith("WebObjects")) { int loc = staticHtmlBase.lastIndexOf('/'); if (loc > 0) { staticHtmlBase = staticHtmlBase.substring(0, loc); } } if (!staticHtmlBase.endsWith("/")) { staticHtmlBase = staticHtmlBase + "/"; } } } else { // If we're not running as a servlet, there's no updating to do log.debug("Skipping static HTML resource updates"); } if (staticHtmlBase != null) { log.debug("attempting to set frameworks Base URL = " + staticHtmlBase); setFrameworksBaseURL(staticHtmlBase); } // Dump any cached data using the previous frameworks base url resourceManager().flushDataCache(); staticHtmlResourcesNeedInitializing = false; log.debug("frameworks Base URL = " + frameworksBaseURL()); } // ---------------------------------------------------------- private void updateStaticHtmlResourcesForFramework( File framework, File staticResourceBaseDir) { File webServerResources = new File(framework, "WebServerResources"); if (webServerResources.isDirectory()) { log.info("Copying static html resources from => " + framework.getName()); try { File target = new File(staticResourceBaseDir, framework.getName() + "/" + webServerResources.getName()); if (target.exists()) { if (target.isDirectory()) { FileUtilities.deleteDirectory(target); } else { target.delete(); } } target.mkdirs(); FileUtilities.copyDirectoryContents( webServerResources, target); } catch (java.io.IOException e) { log.error("Exception copying static html resource from '" + webServerResources + "' to '" + staticResourceBaseDir + "'", e); } } } // ---------------------------------------------------------- private void updateExecutablePermissionsForFramework( String frameworkName, File frameworkDir) { // No need for file perms on windows if (isRunningOnWindows()) { return; } // Otherwise, attempt to add executable permissions to necessary // files String execFileList = configurationProperties() .getProperty(frameworkName + ".chmodx"); if (execFileList != null) { for (String fileName : execFileList.split(",\\s*")) { File file = new File(frameworkDir, fileName); if (file.exists()) { // Make it executable, if possible. // First, build the chmod command line. Start with // file name, and escape any space chars (other shell- // special chars are not escaped!) String cmd = file.toString(); cmd = cmd.replaceAll(" ", "\\\\ "); // Now add the chmod part cmd = "chmod a+x " + cmd; // And any configured shell prefix String shell = org.webcat.core.Application.cmdShell(); if (shell != null && shell.length() > 0) { cmd = shell + cmd; if (shell.charAt(shell.length() - 1) == '"') { cmd += "\""; } } // Execute this command try { log.info(cmd); executeExternalCommand(cmd, file.getParentFile()); } catch (Exception e) { log.error("attempting to execute command:\n" + cmd, e); } } } } } // ---------------------------------------------------------- /** * Gets the message dispatcher used to send Message objects to users. If * no dispatcher is yet registered (by the Notifications subsystem, * primarily), then the default fallback message dispatcher is created so * that messages will be e-mailed. * * @return the message dispatcher used by the application */ public synchronized IMessageDispatcher messageDispatcher() { if (messageDispatcher == null) { messageDispatcher = new FallbackMessageDispatcher(); } return messageDispatcher; } // ---------------------------------------------------------- /** * Sets the message dispatcher used to send Message objects to users. * * @param dispatcher the message dispatcher */ public synchronized void setMessageDispatcher(IMessageDispatcher dispatcher) { messageDispatcher = dispatcher; } // ---------------------------------------------------------- /** * Register all messaging protocols and initializes the system-wide * protocol settings object if it does not exist. */ private void initializeMessagingSystem() { // Register Core messages. ApplicationStartupMessage.register(); UnexpectedExceptionMessage.register(); } // ---------------------------------------------------------- /** * Gets the web API request handler so that other subsystems can add hooks * for their own entities to the external API. * * @return the {@link WebAPIRequestHandler} */ public synchronized WebAPIRequestHandler webAPI() { return apiHandler; } // ---------------------------------------------------------- /** * If the app is running as a servlet, this method checks the version * stamp stored in the {@link net.sf.webcat.WCServletAdaptor} to see if * it is the same as the "expected" version number stored in the Core * subsystem's properties file. The "expected" version is captured at * subsystem build time from the most recent Bootstrap.jar build. * But because the Bootstrap.jar file performs the automatic update * operations, it cannot automatically update itself--instead, it must * be updated manually. This check detects when a new release is * available, and notifies the administrator that a manual update is * needed. * * Instructions for performing a manual update are provided at: * <a href="http://web-cat.cs.vt.edu/WCWiki/UpdateBootstrapJar"> * http://web-cat.cs.vt.edu/WCWiki/UpdateBootstrapJar</a> */ private void checkBootstrapVersion() { String expectedVersion = configurationProperties().getProperty( "bootstrap.project.version"); if (expectedVersion != null) { net.sf.webcat.WCServletAdaptor adaptor = net.sf.webcat.WCServletAdaptor.getInstance(); // Bootstrapping is only enabled when running as a servlet, // so we can only check the version at that time if (adaptor != null) { String currentVersion = null; // Use reflection to get the current version, in case the // current bootstrap version is so old it doesn't even // support version retrieval! try { currentVersion = (String)adaptor.getClass() .getMethod("version").invoke(adaptor); } catch (Exception e) { log.error("exception retrieving Bootstrap.jar version:", e); } if (!expectedVersion.equals(currentVersion)) { log.error("Bootstrap.jar is out of date: expected '" + expectedVersion + "' but found '" + currentVersion + "'"); sendAdminEmail( "Please update your Bootstrap.jar", "During startup, Web-CAT detected that an older " + "version of Bootstrap.jar\nis installed. Web-CAT " + "expected version '" + expectedVersion + "', but found '" + currentVersion + "'.\nThis jar provides Web-CAT's automatic update " + "support and it can only be\nupdated manually.\n\n" + "Please follow these instructions to download and " + "install the latest\navailable version:\n\n" + "http://web-cat.org/WCWiki/" + "UpdateBootstrapJar\n"); } } } else { log.error("Unable to read expected bootstrap.project.version"); sendAdminEmail( "Unable to verify bootstrap version", "During startup, Web-CAT was unable to read the expected\n" + "bootstrap.project.version. Your Web-CAT instance will " + "continue\nrunning normally. Please contact the Web-CAT " + "team for assistance\nresolving this problem.\n"); } } // ---------------------------------------------------------- /** * Gets the path to the Web-CAT storage directory (where submissions and * object file stores are located). * * @return the path to the Web-CAT storage directory */ public static File storageDirectory() { // TODO can we rename this property easily? Find all references to it // and pass it through this method instead. String path = configurationProperties().getProperty( "grader.submissiondir"); return new File(path); } // ---------------------------------------------------------- /** * Gets the path to the repository root area inside the Web-CAT storage * directory. * * @return the path to the repository root area */ private static File repositoryRootDirectory() { return new File(storageDirectory(), "_Repositories"); } // ---------------------------------------------------------- /** * Gets a value indicating whether a repository currently exists for the * specified object. * * @param object the object * @return true if the repository currently exists, false if it has not yet * been created */ public boolean repositoryExistsForObject(EOBase object) { if (object instanceof RepositoryProvider) { File fsDir = new File(repositoryRootDirectory(), object.entityName() + "/" + object.apiId()); return fsDir.exists(); } else { return false; } } // ---------------------------------------------------------- /** * Gets the path to the repository for the specified object. This path * points to a bare Git repository; to access the repository directly, use * the {@link GitUtilities#repositoryForObject(EOEnterpriseObject)} method * instead of this one, which also creates the repository on demand if it * does not yet exist. * * @param object the EO to get the repository location for * @return the path to the repository for the EO, or null if the EO is not * a file store provider */ public File repositoryPathForObject(EOBase object) { if (object instanceof RepositoryProvider) { File fsDir = new File(repositoryRootDirectory(), object.entityName() + "/" + object.apiId()); if (!fsDir.exists()) { fsDir.mkdirs(); } return fsDir; } else { return null; } } // ---------------------------------------------------------- /** * Gets the path to the temporary working copy for the specified object. * This path will be used to determine where to checkout files in an EO's * file repository, so that they can be exposed for protocols such as * Web-DAV. * * @param object the EO to get the working copy location for * @return the path to the working copy for the EO, or null if the EO is * not a file store provider */ public File workingCopyPathForObject(EOBase object) { if (object instanceof RepositoryProvider) { File fsDir = new File(repositoryRootDirectory(), "_WorkingCopies/" + object.entityName() + "/" + object.apiId()); return fsDir; } else { return null; } } // ---------------------------------------------------------- /** * Gets an instance of {@link RelativeTimestampFormatter} that performs * approximate date formatting relative to the current time. * * @return an instance of {@link RelativeTimestampFormatter} that performs * approximate date formatting relative to the current time */ public RelativeTimestampFormatter approximateRelativeTimestampFormatter() { if (approximateRelativeTimestampFormatter == null) { approximateRelativeTimestampFormatter = new RelativeTimestampFormatter(true); } return approximateRelativeTimestampFormatter; } // ---------------------------------------------------------- /** * Gets an instance of {@link RelativeTimestampFormatter} that performs * exact date formatting relative to the current time. * * @return an instance of {@link RelativeTimestampFormatter} that performs * exact date formatting relative to the current time */ public RelativeTimestampFormatter exactRelativeTimestampFormatter() { if (exactRelativeTimestampFormatter == null) { exactRelativeTimestampFormatter = new RelativeTimestampFormatter(true); } return exactRelativeTimestampFormatter; } // ---------------------------------------------------------- /** * Gets an instance of {@link FileSizeFormatter} that can be used to format * file sizes in components. * * @return an instance of {@link FileSizeFormatter} */ public FileSizeFormatter fileSizeFormatter() { if (fileSizeFormatter == null) { fileSizeFormatter = new FileSizeFormatter(); } return fileSizeFormatter; } // ---------------------------------------------------------- private static void loadArchiveManagers() { @SuppressWarnings("unchecked") NSArray<String> handlers = (NSArray<String>)configurationProperties() .arrayForKey("Core.archive.handler.list"); if (handlers != null) { ArchiveManager manager = ArchiveManager.getInstance(); for (String name : handlers) { try { manager.addHandler( (IArchiveHandler)DelegatingUrlClassLoader .getClassLoader().loadClass(name).newInstance()); } catch (Exception e) { log.error("Exception loading archive handler: ", e); } } } } //~ Instance/static variables ............................................. // Force the ERXExtensions bundle to be initialized before this class by // referencing it here. @SuppressWarnings("unused") private static ERXExtensions forcedInitialization1 = null; @SuppressWarnings("unused") private static ERXProperties forcedInitialization2 = null; public static int userCount = 0; private static final int maxAttachmentSize = 100000; // bytes // Add more options here (e.g. for IIS, NSAPI, etc.), if necessary... private static final NSArray<String> HOST_NAME_KEYS = new NSArray<String>( new String[]{ "x-webobjects-server-name", "server_name", "Host", "http_host"}); // Add more options here (e.g. for IIS, NSAPI, etc.), if necessary... private static final NSArray<String> SERVER_PORT_KEYS = new NSArray<String>(new String[]{ "x-webobjects-server-port", "SERVER_PORT"}); private static String version; private static NSTimestamp startTime = new NSTimestamp(); private static NSTimestamp dieTime = null; private static String deathMessage = null; private static WCConfigurationFile __properties; private static String __appIdentifier; private static SubsystemManager __subsystemManager; private static String cmdShell; private static Boolean defaultsToSecure; private static String urlHostPrefix; private static boolean staticHtmlResourcesNeedInitializing = true; private static boolean initializationComplete = false; private static boolean needsInstallation = true; private IMessageDispatcher messageDispatcher; private WebAPIRequestHandler apiHandler; private EOEditingContext errorLoggingContext; private RelativeTimestampFormatter approximateRelativeTimestampFormatter; private RelativeTimestampFormatter exactRelativeTimestampFormatter; private FileSizeFormatter fileSizeFormatter; static Logger log = Logger.getLogger(Application.class); static Logger requestLog = Logger.getLogger(Application.class.getName() + ".requests"); }