/*
* 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.extensions.logging;
import java.util.Enumeration;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Level;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.LoggingEvent;
import com.webobjects.appserver.WOApplication;
import com.webobjects.appserver.WOComponent;
import com.webobjects.appserver.WOContext;
import com.webobjects.appserver.WOMailDelivery;
import com.webobjects.eocontrol.EOQualifier;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSKeyValueCoding;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import er.extensions.appserver.ERXApplication;
import er.extensions.appserver.ERXWOContext;
import er.extensions.foundation.ERXConfigurationManager;
import er.extensions.foundation.ERXUtilities;
import er.extensions.foundation.ERXValueUtilities;
/**
* Basic log4j Mail Message Appender.<br>
* Used for logging log events that will eventually be emailed
* out. Logs events using {@link com.webobjects.appserver.WOMailDelivery WOMailDelivery}.<br><br>
* Mandatory Fields:<br>
* ToAddresses - comma separated list of email addresses to send the log event
* message to.<br>
* FromAddress - Who the message is from, if left blank then DomainName is a
* mandatory field.<br><br>
* Optional Fields:<br>
* BccAddresses - comma separated list of email address to bcc on the email<br>
* CcAddresses - comma separated list of email address to cc on the email<br>
* ReplyTo - reply to address<br>
* DomainName - When generating a from email address, used for the bit after the
* "@", ie foo@bar.com, the domain name is 'bar.com'.<br>
* HostName - When generating an email address from, defaults to name of the
* localhost.<br>
* ExceptionPage - name of the exception page, is unset, <br>
* Title - Title of the email messages, if not specified the title will be a
* truncated version of the log message.<br>
* Qualifier - qualifier that defines if the event should be logged.
*/
public class ERXMailAppender extends AppenderSkeleton {
/** holds the from address */
protected String fromAddress;
/** holds the computed from address */
protected String computedFromAddress;
/** holds the reply to address */
protected String replyTo;
/** holds the to addresses */
protected String toAddresses;
/** holds the cc addresses */
protected String ccAddresses;
/** holds the bcc addresses */
protected String bccAddresses;
/** holds the domain */
protected String domainName;
/** holds the qualifier */
protected String qualifier;
/** holds the qualifier */
protected EOQualifier realQualifier;
/** holds the title */
protected String title;
/** holds the exception page name */
protected String exceptionPageName;
/** holds the host name */
protected String hostName;
protected String formatAsError;
protected String titleIncludesPriorityLevel;
protected String titleIncludesApplicationName;
/** holds the flag if all the conditions for logging have been checked */
protected boolean conditionsChecked = false;
/**
* Public constructor.
*/
public ERXMailAppender() {
closed = false;
}
/**
* The mail message appender does require
* a layout.
* @return true.
*/
public boolean requiresLayout() { return true; }
/**
* When closed set the state to closed. Closes
* the current appender.
*/
public void close() { closed = true; }
/**
* Gets the qualifier as a string.
* @return the qualifier as string.
*/
public String getQualifier() {
return qualifier;
}
/**
* Sets the qualifier as a string.
* @param qualifier the qualifier as string.
*/
public void setQualifier(String qualifier) {
this.qualifier = qualifier;
realQualifier = null;
}
/**
* Gets the qualifier.
* @return the from address.
*/
public EOQualifier realQualifier() {
if(realQualifier == null) {
if(qualifier == null) {
return null;
}
realQualifier = EOQualifier.qualifierWithQualifierFormat(qualifier, null);
}
return realQualifier;
}
/**
* Gets the from address set by the user.
* @return the from address.
*/
public String getFromAddress() {
return fromAddress;
}
/**
* Sets the from address.
* @param fromAddress to use when generating emails.
*/
public void setFromAddress(String fromAddress) { this.fromAddress = fromAddress; }
/**
* Gets the reply to address set by the user.
* @return the reply to address.
*/
public String getReplyTo() {
return replyTo;
}
/**
* Sets the reply to address.
* @param replyTo to address to use when generating emails.
*/
public void setReplyTo(String replyTo) { this.replyTo = replyTo; }
/**
* Gets the from address for the appender.
* If a from address is not specified then
* an address is constructed in the form:
* applicationName-hostName@domainName.
* @return the from address.
*/
public String computedFromAddress() {
if (computedFromAddress == null) {
if (getFromAddress() != null) {
computedFromAddress = getFromAddress();
} else {
computedFromAddress = WOApplication.application().name()+"-"+getHostName()+"@" + getDomainName();
}
}
return computedFromAddress;
}
/**
* Gets the to addresses as a string.
* @return to addresses as a string
*/
public String getToAddresses() {
return toAddresses;
}
/**
* Sets the to addresses as a string.
* @param toAddresses comma separated array of email addresses
*/
public void setToAddresses(String toAddresses) {
this.toAddresses = toAddresses;
}
/**
* Gets the to addresses as an array.
* @return the to addresses as an array.
*/
public NSArray toAddressesAsArray() {
return toAddresses != null ? NSArray.componentsSeparatedByString(toAddresses, ",") : NSArray.EmptyArray;
}
/**
* Sets the to addresses as a string.
* @param ccAddresses comma separated array of email addresses
*/
public void setCcAddresses(String ccAddresses) {
this.ccAddresses = ccAddresses;
}
/**
* Gets the cc addresses as a String.
* @return cc addresses as a string
*/
public String ccAddresses() {
return ccAddresses;
}
/**
* Gets the cc addresses as an array.
* @return the cc addresses as an array.
*/
public NSArray ccAddressesAsArray() {
return ccAddresses != null ? NSArray.componentsSeparatedByString(ccAddresses, ",") : NSArray.EmptyArray;
}
/**
* Sets the bcc addresses as a string.
* @param bccAddresses comma separated array of email addresses
*/
public void setBccAddresses(String bccAddresses) {
this.bccAddresses = bccAddresses;
}
/**
* Gets the bcc addresses as a String.
* @return bcc addresses as a string
*/
public String bccAddresses() {
return bccAddresses;
}
/**
* Gets the bcc addresses as an array.
* @return the bcc addresses as an array.
*/
public NSArray bccAddressesAsArray() {
return bccAddresses != null ? NSArray.componentsSeparatedByString(bccAddresses, ",") : NSArray.EmptyArray;
}
/**
* Gets the exception page name.
* @return exception page name.
*/
public String getExceptionPageName() { return exceptionPageName; }
/**
* Sets the title.
* @param exceptionPageName title of the mail message
*/
public void setExceptionPageName(String exceptionPageName) { this.exceptionPageName = exceptionPageName; }
/**
* Gets the title.
* @return title.
*/
public String getTitle() { return title; }
/**
* Sets the title.
* @param title of the mail message
*/
public void setTitle(String title) { this.title = title; }
/**
* Gets the host name. If one is not specified then the host
* name of the machine will be used.
* @return host name to use when constructing the 'from' email
* address.
*/
public String getHostName() {
if (hostName == null)
hostName = ERXConfigurationManager.defaultManager().hostName();
return hostName;
}
/**
* Sets the host name to use for the given appender.
* @param hostName for the appender.
*/
public void setHostName(String hostName) { this.hostName = hostName; }
/**
* Gets the domain name.
* @return domain name.
*/
public String getDomainName() {
return domainName;
}
/**
* Sets the domain name.
* @param domainName new domain name
*/
public void setDomainName(String domainName) {
this.domainName = domainName;
}
public String formatAsError() {
return formatAsError;
}
public void setFormatAsError(String value) {
formatAsError = value;
}
public boolean formatAsErrorAsBoolean() {
return ERXValueUtilities.booleanValueWithDefault(formatAsError(), false);
}
public String titleIncludesPriorityLevel() {
return titleIncludesPriorityLevel;
}
public void setTitleIncludesPriorityLevel(String value) {
titleIncludesPriorityLevel = value;
}
public boolean titleIncludesPriorityLevelAsBoolean() {
return ERXValueUtilities.booleanValueWithDefault(titleIncludesPriorityLevel(), true);
}
public String titleIncludesApplicationName() {
return titleIncludesApplicationName;
}
public void setTitleIncludesApplicationName(String value) {
titleIncludesApplicationName = value;
}
public boolean titleIncludesApplicationNameAsBoolean() {
return ERXValueUtilities.booleanValueWithDefault(titleIncludesApplicationName(), true);
}
/**
* Used to determine if the system is ready to log
* events with MERCMailDelivery.
* @return true if all of the conditions are satisfied
*/
protected boolean checkConditions() {
if (getFromAddress() == null && getDomainName() == null) {
LogLog.error("Attempting to log an event with a null domain name and a null from address!");
} else if (toAddressesAsArray().count() == 0) {
LogLog.error("Attempting to log with an empty array of toAddresses");
} else if (layout == null) {
LogLog.warn("Attempting to log an event to an ERCMailMessageAppender without a layout specified.");
} else {
conditionsChecked = true;
}
return conditionsChecked;
}
/**
* Entry point for logging an event.
*
* Reminder: the nesting of calls is:
*
* doAppend()
* - check threshold
* - filter
* - append();
* - checkConditions();
* - subAppend();
*
* @param event current logging event
*/
@Override
public void append(LoggingEvent event) {
if (conditionsChecked || checkConditions()) {
if(event.getLevel().equals(Level.ERROR)
|| event.getLevel().equals(Level.FATAL)) {
EOQualifier q = realQualifier();
if(q == null || q.evaluateWithObject(event)) {
subAppend(event);
}
}
} else {
LogLog.warn("Unable to log event: " + event.getMessage());
}
}
public String composeTitle(LoggingEvent event) {
String composeTitle;
if (getTitle() != null) {
composeTitle = getTitle();
} else {
StringBuilder temp = new StringBuilder();
if (titleIncludesPriorityLevelAsBoolean()) {
temp.append(event.getLevel().toString() + ": ");
}
if (titleIncludesApplicationNameAsBoolean()) {
temp.append(WOApplication.application().name() + ": ");
}
temp.append(event.getRenderedMessage());
composeTitle = temp.toString();
int ret = temp.indexOf("\n");
if(ret > 0) {
composeTitle = composeTitle.substring(0, ret);
}
}
return composeTitle;
}
/**
* In case we generate a HTML page, we construct a dictionary with the entries that
* is later pushed into the page. This is the place to override and extend in case you want to
* provide your own pages. Current keys are:
* extraInfo - NSDictionary of extra info from the application
* errorMessage - the title for the message
* formattedMessage - the actual message
* exception - the throwable given by the event
* reasonLines - NSArray of strings that give the backtrace
* @param event logging event
*/
public NSMutableDictionary composeExceptionPageDictionary(LoggingEvent event) {
NSMutableDictionary result = new NSMutableDictionary();
WOContext currentContext = ERXWOContext.currentContext();
NSDictionary extraInformation = null;
if (currentContext != null) {
extraInformation = ERXApplication.erxApplication().extraInformationForExceptionInContext(null, currentContext);
result.setObjectForKey(extraInformation, "extraInfo");
}
String composeTitle = composeTitle(event);
if(composeTitle != null) {
result.setObjectForKey(composeTitle, "errorMessage");
}
String message = layout.format(event);
if(message != null) {
result.setObjectForKey(message, "formattedMessage");
}
if (event.getThrowableInformation() != null && event.getThrowableInformation().getThrowable() != null) {
result.setObjectForKey(event.getThrowableInformation().getThrowable(), "exception");
} else {
NSArray parts = NSArray.componentsSeparatedByString(ERXUtilities.stackTrace(), "\n");
NSMutableArray subParts = new NSMutableArray();
boolean first = true;
for (Enumeration e = parts.reverseObjectEnumerator(); e.hasMoreElements();) {
String element = (String)e.nextElement();
if (element.indexOf("org.apache.log4j") != -1)
break;
if (!first)
subParts.insertObjectAtIndex(element, 0);
else
first = false;
}
result.setObjectForKey(subParts, "reasonLines");
}
return result;
}
/**
* Where the actual logging event is processed and a
* mail message is generated.
* @param event logging event
* @return mail body
*/
public String composeMessage(LoggingEvent event) {
String result;
if(getExceptionPageName() != null && ERXValueUtilities.booleanValue(formatAsError())) {
NSDictionary dict = composeExceptionPageDictionary(event);
WOComponent page = ERXApplication.instantiatePage(exceptionPageName);
for(Enumeration keys = dict.keyEnumerator(); keys.hasMoreElements();) {
String key = (String) keys.nextElement();
Object value = dict.objectForKey(key);
try {
page.takeValueForKey(value, key);
} catch(NSKeyValueCoding.UnknownKeyException e) {
}
}
try {
result = page.generateResponse().contentString();
} catch(Exception ex) {
LogLog.error("Can't create response!", ex);
result = dict.objectForKey("formattedMessage") + "\n";
result = dict.objectForKey("extraInfo") + "\n";
NSArray lines = (NSArray)dict.objectForKey("reasonLines");
if(lines != null) {
result = lines.componentsJoinedByString("\n");
}
}
} else {
result = layout.format(event);
result = result + "\n" + ERXUtilities.stackTrace();
}
return result;
}
/**
* Where the actual logging event is processed and a
* mail message is generated.
* @param event logging event
*/
public void subAppend(LoggingEvent event) {
WOMailDelivery delivery = WOMailDelivery.sharedInstance();
String composeTitle = composeTitle(event);
String content = composeMessage(event);
String message = delivery.composePlainTextEmail(computedFromAddress(),
toAddressesAsArray(), ccAddressesAsArray(), composeTitle, content, false);
if(getExceptionPageName() != null && ERXValueUtilities.booleanValue(formatAsError())) {
message = "Content-Type: text/html\n" + message;
}
if(bccAddresses() != null) {
message = "BCC: "+bccAddresses()+"\n" + message;
}
//LogLog.error(message);
delivery.sendEmail(message);
}
}