/* * Copyright (C) NetStruxr, Inc. All rights reserved. * * This software is published under the terms of the NetStruxr * Public Software License version 0.5, a copy of which has been * included with this distribution in the LICENSE.NPL file. */ package er.corebusinesslogic; import java.lang.reflect.InvocationTargetException; import java.sql.SQLException; import java.util.Enumeration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.webobjects.appserver.WOApplication; import com.webobjects.eoaccess.EOAdaptorChannel; import com.webobjects.eoaccess.EODatabaseContext; import com.webobjects.eoaccess.EOEntity; import com.webobjects.eoaccess.EOEntityClassDescription; import com.webobjects.eoaccess.EOGeneralAdaptorException; import com.webobjects.eoaccess.EOJoin; import com.webobjects.eoaccess.EOModelGroup; import com.webobjects.eoaccess.EORelationship; import com.webobjects.eoaccess.EOUtilities; import com.webobjects.eocontrol.EOEditingContext; import com.webobjects.eocontrol.EOEnterpriseObject; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSForwardException; import com.webobjects.foundation.NSLog; import er.corebusinesslogic.audittrail.ERCAuditTrailHandler; import er.directtoweb.ERDirectToWeb; import er.extensions.ERXExtensions; import er.extensions.ERXFrameworkPrincipal; import er.extensions.appserver.ERXApplication; import er.extensions.eof.ERXEC; import er.extensions.eof.ERXEOControlUtilities; import er.extensions.foundation.ERXConfigurationManager; import er.extensions.foundation.ERXProperties; import er.extensions.foundation.ERXStringUtilities; import er.extensions.foundation.ERXThreadStorage; import er.extensions.foundation.ERXUtilities; import er.extensions.jdbc.ERXJDBCUtilities; import er.extensions.jdbc.ERXSQLHelper; import er.javamail.ERJavaMail; /** * * @property er.corebusinesslogic.ERCoreBusinessLogic.ProblemEmailDomain * @property er.corebusinesslogic.ERCoreBusinessLogic.ProblemEmailRecipients * @property er.corebusinesslogic.ERCoreBusinessLogic.ShouldMailExceptions */ public class ERCoreBusinessLogic extends ERXFrameworkPrincipal { // =========================================================================== // Class constant(s) // --------------------------------------------------------------------------- private static final Logger log = LoggerFactory.getLogger(ERCoreBusinessLogic.class); /** property key that holds the email domain of the generated from email */ public static final String ProblemEmailDomainPropertyKey = "er.corebusinesslogic.ERCoreBusinessLogic.ProblemEmailDomain"; /** property key that holds the emails of those who recieve problem emails */ public static final String ProblemEmailRecipientsPropertyKey = "er.corebusinesslogic.ERCoreBusinessLogic.ProblemEmailRecipients"; // =========================================================================== // Class variable(s) // --------------------------------------------------------------------------- /** holds the shared instance reference */ protected static ERCoreBusinessLogic sharedInstance; public final static Class REQUIRES[] = new Class[] {ERXExtensions.class, ERDirectToWeb.class, ERJavaMail.class}; /** * Registers the class as the framework principal */ static { setUpFrameworkPrincipalClass(ERCoreBusinessLogic.class); } // =========================================================================== // Class method(s) // --------------------------------------------------------------------------- /** * Gets the shared instance of the ERCoreBusinessLogic. * @return shared instance. */ public static ERCoreBusinessLogic sharedInstance() { if (sharedInstance == null) { sharedInstance = ERXFrameworkPrincipal.sharedInstance(ERCoreBusinessLogic.class); } return sharedInstance; } /** * Sets the actor in the current thread storage. * @param actor current user for this thread */ public static void setActor(EOEnterpriseObject actor) { log.debug("Setting actor to: {}", actor); if (actor != null) { ERXThreadStorage.takeValueForKey(actor, "actor"); } else { ERXThreadStorage.removeValueForKey("actor"); } } /** * Gets the actor as a local instance in the given context. * @param ec editing context to pull a local copy of the actor * into * @return actor instance in the given editing context */ public static EOEnterpriseObject actor(EOEditingContext ec) { EOEnterpriseObject actor = actor(); if (actor != null && actor.editingContext() != ec) { EOEditingContext actorEc = actor.editingContext(); actorEc.lock(); try { EOEnterpriseObject localActor = ERXEOControlUtilities.localInstanceOfObject(ec, actor); try { if(actor instanceof ERCoreUserInterface) { NSArray prefs = ((ERCoreUserInterface)actor).preferences(); prefs = ERXEOControlUtilities.localInstancesOfObjects(ec, prefs); ((ERCoreUserInterface)localActor).setPreferences(prefs); } } catch(RuntimeException ex) { log.error("Error while setting getting actor's preferences: ", actor, ex); } actor = localActor; } finally { actorEc.unlock(); } } return actor; } /** * Gets the actor. * @return current actor for the thread */ public static EOEnterpriseObject actor() { return (EOEnterpriseObject)ERXThreadStorage.valueForKey( "actor"); } public static String staticStoredValueForKey(String key) { return ERCStatic.clazz.staticStoredValueForKey(key); } public static int staticStoredIntValueForKey(String key) { return ERCStatic.clazz.staticStoredIntValueForKey(key); } public static String staticStoredValueForKey(String key, boolean noCache) { return ERCStatic.clazz.staticStoredValueForKey(key, noCache); } public static int staticStoredIntValueForKey(String key, boolean noCache) { return ERCStatic.clazz.staticStoredIntValueForKey(key, noCache); } public static String staticStoredValueForKey(String key, EOEditingContext ec) { return ERCStatic.clazz.staticStoredValueForKey(ec, key); } public static int staticStoredIntValueForKey(String key, EOEditingContext ec) { return ERCStatic.clazz.staticStoredIntValueForKey(ec, key); } public static void takeStaticStoredValueForKey(String value, String key, EOEditingContext editingContext) { ERCStatic.clazz.takeStaticStoredValueForKey(editingContext, value, key); } public static void takeStaticStoredValueForKey(String value, String key) { ERCStatic.clazz.takeStaticStoredValueForKey(value, key); } public static void invalidateStaticValueForKeyCache() { ERCStatic.clazz.invalidateCache(); } // =========================================================================== // Instance variable(s) // --------------------------------------------------------------------------- /** caches the email addresses to send to in the case of problems */ protected NSArray _emailsForProblemRecipients; /** caches the problem email address domain */ protected String _problemEmailDomain; // =========================================================================== // Instance method(s) // --------------------------------------------------------------------------- /** * Called when it is time to finish the * initialization of the framework. */ @Override public void finishInitialization() { ERCAuditTrailHandler.initialize(); ERCStampedEnterpriseObject.initialize(); // Initialized shared data initializeSharedData(); // Register handlers for user preferences. ERCoreUserPreferences.userPreferences().registerHandlers(); log.debug("ERCoreBusinessLogic: finishInitialization"); } /** * Initializes the shared eof data. */ public void initializeSharedData() { // Shared Data Init Point. Keep alphabetical Class c = ERCMailState.class; } public boolean shouldMailReportedExceptions() { return ERXProperties.booleanForKey("er.corebusinesslogic.ERCoreBusinessLogic.ShouldMailExceptions"); } public void addPreferenceRelationshipToActorEntity(String entityName) { EOEntity entity = EOModelGroup.defaultGroup().entityNamed(entityName); if(entity != null && entity.primaryKeyAttributeNames().count() == 1) { addPreferenceRelationshipToActorEntity(entityName, entity.primaryKeyAttributeNames().lastObject()); } else { throw new IllegalArgumentException("Entity is not suitable: " + entityName); } } /** * Registers a run-time relationship called "preferences" on the actor * entity of your business logic. The framework needs preferences * relationship to access user preferences for a specific actor. * Call this method when you initialize your business logic layer. * (Check BTBusinessLogic class as an example.) * * @param entityName String name for your actor entity * @param attributeNameToJoin String attribute name on the actor * entity; used by the relationship and typically it's the * primary key. */ public void addPreferenceRelationshipToActorEntity(String entityName, String attributeNameToJoin) { EOEntity actor = EOModelGroup.defaultGroup().entityNamed(entityName); EOEntity preference = EOModelGroup.defaultGroup().entityNamed("ERCPreference"); EOJoin preferencesJoin = new EOJoin(actor.attributeNamed(attributeNameToJoin), preference.attributeNamed("userID")); EORelationship preferencesRelationship = new EORelationship(); preferencesRelationship.setName("preferences"); actor.addRelationship(preferencesRelationship); preferencesRelationship.addJoin(preferencesJoin); preferencesRelationship.setToMany(true); preferencesRelationship.setJoinSemantic(EORelationship.InnerJoin); preferencesRelationship.setDeleteRule(EOEntityClassDescription.DeleteRuleCascade); EOJoin userJoin = new EOJoin(preference.attributeNamed("userID"), actor.attributeNamed(attributeNameToJoin) ); EORelationship userRelationship = new EORelationship(); userRelationship.setName("user"); preference.addRelationship(userRelationship); userRelationship.addJoin(userJoin); userRelationship.setToMany(false); userRelationship.setJoinSemantic(EORelationship.InnerJoin); } /** * Gets the array of email addresses to send emails * about problems to. * @return array of email addresses */ public NSArray emailsForProblemRecipients() { if (_emailsForProblemRecipients == null) { _emailsForProblemRecipients = ERXProperties.arrayForKeyWithDefault(ProblemEmailRecipientsPropertyKey, NSArray.EmptyArray); } return _emailsForProblemRecipients; } /** * Sets the emails for problem recipients. Should * be an array of email addresses to report exceptions * to in production applications. * @param a array of email addresses */ public void setEmailsForProblemRecipients(NSArray a) { _emailsForProblemRecipients=a; } /** * Gets the problem email domain. This is used for constructing the * from address when reporting an exception. Should be of the form: * foo.com. * @return problem email address domain */ public String problemEmailDomain() { if (_problemEmailDomain == null) { _problemEmailDomain = System.getProperty(ProblemEmailDomainPropertyKey); } return _problemEmailDomain; } /** * Sets the problem email domain. * @param value to set problem domain to */ public void setProblemEmailDomain(String value) { _problemEmailDomain = value; } public synchronized String extraInfoString(NSDictionary extraInfo, int indent) { StringBuffer s = new StringBuffer(); ERXStringUtilities.indent(s, indent); s.append("Extra Information: \n"); ERXStringUtilities.indent(s, indent); s.append(" Actor = " + (actor() != null ? actor().toString() : "No Actor") + "\n"); if (extraInfo != null && extraInfo.count() > 0) { for (Enumeration keyEnumerator = extraInfo.keyEnumerator(); keyEnumerator.hasMoreElements();) { String key = (String)keyEnumerator.nextElement(); Object value = extraInfo.objectForKey(key); if (value instanceof NSDictionary) { String valueStr = String.valueOf(value); StringBuffer valueIndent = new StringBuffer(); valueIndent.append("\n "); ERXStringUtilities.indent(valueIndent, indent); for (int i = 0; i < key.length(); i ++) { valueIndent.append(' '); } value = valueStr.replaceAll("\n", valueIndent.toString()); } ERXStringUtilities.indent(s, indent); s.append(" " + key + " = " + value + "\n"); } } return s.toString(); } /** * Reports an exception. If caching is enabled then the exception * will also be emailed to the problem mail recipients. * @param exception to be reported * @param extraInfo dictionary of extra information about what was * happening when the exception was thrown. */ public synchronized void reportException(Throwable exception, NSDictionary extraInfo) { if (exception instanceof NSForwardException) { exception = ((NSForwardException)exception).originalException(); } StringBuilder s = new StringBuilder(); try { s.append(" **** Caught: "+exception + "\n"); s.append(extraInfoString(extraInfo, 3)); if (exception instanceof EOGeneralAdaptorException) { EOGeneralAdaptorException e= (EOGeneralAdaptorException)exception; if (e.userInfo()!=null) { Object userInfo=e.userInfo(); if (userInfo instanceof NSDictionary) { NSDictionary uid=(NSDictionary)userInfo; for (Enumeration e2=uid.keyEnumerator(); e2.hasMoreElements();) { String key=(String)e2.nextElement(); Object value=uid.objectForKey(key); s.append(key + " = " + value + ";\n"); } } else { s.append(e.userInfo().toString()); } } } else { s.append(ERXUtilities.stackTrace(exception)); } if (!WOApplication.application().isCachingEnabled() || !ERCMailDelivery.usesMail() || !shouldMailReportedExceptions()) { log.error("{}", s); } else { // Usually the Mail appender is set to Threshold ERROR log.warn("{}", s); if (emailsForProblemRecipients().count() == 0 || problemEmailDomain() == null) { log.error("Unable to log problem due to misconfiguration: recipients: {}" + " email domain: {}", emailsForProblemRecipients(), problemEmailDomain()); } else { ERCMailableExceptionPage standardExceptionPage = (ERCMailableExceptionPage)ERXApplication.instantiatePage("ERCMailableExceptionPage"); standardExceptionPage.setException(exception); standardExceptionPage.setActor(actor()); standardExceptionPage.setExtraInfo(extraInfo); EOEditingContext ec = ERXEC.newEditingContext(); ec.lock(); try { String shortExceptionName; Throwable exceptionForTitle = exception; if (exception instanceof InvocationTargetException) { exceptionForTitle = ((InvocationTargetException)exception).getTargetException(); } shortExceptionName = ERXStringUtilities.lastPropertyKeyInKeyPath(exceptionForTitle.getClass().getName()); String hostName = ERXConfigurationManager.defaultManager().hostName(); ERCMailDelivery.sharedInstance().composeEmail(WOApplication.application().name()+"-"+hostName+"@"+problemEmailDomain(), emailsForProblemRecipients(), null, null, WOApplication.application().name() + ": " + shortExceptionName + ": " + exceptionForTitle.getMessage(), standardExceptionPage.generateResponse().contentString(), ec); ec.saveChanges(); } finally { ec.unlock(); } ec.dispose(); } } } catch (Throwable u) { try { s.append("************ Caught exception "+u+" trying to report another one: "+exception); s.append("** Original exception "); s.append(ERXUtilities.stackTrace(exception)); s.append("** Second exception "); s.append(ERXUtilities.stackTrace(u)); NSLog.err.appendln(s.toString()); log.error("{}", s); } catch (Throwable u2) {} // WE DON'T WANT ANYTHING TO GO WRONG IN HERE as it would cause the app to instantly exit } } public void createTables(EOEditingContext ec, String modelName) throws SQLException { // AK: FIXME we should try with the DROP options enabled and re-try with the options disabled // so we catch the case when we EODatabaseContext databaseContext = EOUtilities.databaseContextForModelNamed(ec, modelName); ERXSQLHelper helper = ERXSQLHelper.newSQLHelper(databaseContext); String sql = helper.createSchemaSQLForEntitiesInModelWithName(null, modelName); NSArray sqls = helper.splitSQLStatements(sql); EOAdaptorChannel channel = databaseContext.availableChannel().adaptorChannel(); ERXJDBCUtilities.executeUpdateScript(channel, sql); } public void createTables(EOEditingContext ec) throws SQLException { createTables(ec, "ERMail"); createTables(ec, "ERCoreBusinessLogic"); } }