001: /*
002: * $Id: AbstractExceptionListener.java 11343 2008-03-13 10:58:26Z tcarlson $
003: * --------------------------------------------------------------------------------------
004: * Copyright (c) MuleSource, Inc. All rights reserved. http://www.mulesource.com
005: *
006: * The software in this package is published under the terms of the CPAL v1.0
007: * license, a copy of which has been included with this distribution in the
008: * LICENSE.txt file.
009: */
010:
011: package org.mule;
012:
013: import org.mule.api.MessagingException;
014: import org.mule.api.MuleContext;
015: import org.mule.api.MuleEvent;
016: import org.mule.api.MuleEventContext;
017: import org.mule.api.MuleException;
018: import org.mule.api.MuleMessage;
019: import org.mule.api.context.MuleContextAware;
020: import org.mule.api.endpoint.EndpointURI;
021: import org.mule.api.endpoint.ImmutableEndpoint;
022: import org.mule.api.endpoint.InboundEndpoint;
023: import org.mule.api.endpoint.InvalidEndpointTypeException;
024: import org.mule.api.endpoint.OutboundEndpoint;
025: import org.mule.api.lifecycle.Disposable;
026: import org.mule.api.lifecycle.Initialisable;
027: import org.mule.api.lifecycle.InitialisationException;
028: import org.mule.api.lifecycle.LifecycleException;
029: import org.mule.api.lifecycle.LifecycleTransitionResult;
030: import org.mule.api.routing.RoutingException;
031: import org.mule.api.transaction.Transaction;
032: import org.mule.api.transaction.TransactionException;
033: import org.mule.config.ExceptionHelper;
034: import org.mule.config.i18n.CoreMessages;
035: import org.mule.context.notification.ExceptionNotification;
036: import org.mule.message.ExceptionMessage;
037: import org.mule.transaction.TransactionCoordination;
038: import org.mule.transport.NullPayload;
039:
040: import java.beans.ExceptionListener;
041: import java.util.Iterator;
042: import java.util.List;
043:
044: import edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArrayList;
045: import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean;
046:
047: import org.apache.commons.logging.Log;
048: import org.apache.commons.logging.LogFactory;
049:
050: /**
051: * <code>AbstractExceptionListener</code> is a base implementation that custom
052: * Exception Listeners can override. It provides template methods for handling the
053: * for base types of exceptions plus allows multimple endpoints to be associated with
054: * this exception listener and provides an implementation for dispatching exception
055: * events from this Listener.
056: */
057: public abstract class AbstractExceptionListener implements
058: ExceptionListener, Initialisable, Disposable, MuleContextAware {
059: /**
060: * logger used by this class
061: */
062: protected transient Log logger = LogFactory.getLog(getClass());
063:
064: protected List endpoints = new CopyOnWriteArrayList();
065:
066: protected AtomicBoolean initialised = new AtomicBoolean(false);
067:
068: protected MuleContext muleContext;
069:
070: public void setMuleContext(MuleContext context) {
071: this .muleContext = context;
072: }
073:
074: public List getEndpoints() {
075: return endpoints;
076: }
077:
078: public void setEndpoints(List endpoints) {
079: if (endpoints != null) {
080: this .endpoints.clear();
081: // Ensure all endpoints are response endpoints
082: // This will go when we start dropping suport for 1.4 and start using 1.5
083: for (Iterator it = this .endpoints.iterator(); it.hasNext();) {
084: ImmutableEndpoint endpoint = (ImmutableEndpoint) it
085: .next();
086: if (!(endpoint instanceof InboundEndpoint)) {
087: throw new InvalidEndpointTypeException(CoreMessages
088: .exceptionListenerMustUseOutboundEndpoint(
089: this , endpoint));
090: }
091: }
092: this .endpoints.addAll(endpoints);
093: } else {
094: throw new IllegalArgumentException(
095: "List of endpoints = null");
096: }
097: }
098:
099: public void addEndpoint(OutboundEndpoint endpoint) {
100: if (endpoint != null) {
101: endpoints.add(endpoint);
102: }
103: }
104:
105: public boolean removeEndpoint(OutboundEndpoint endpoint) {
106: return endpoints.remove(endpoint);
107: }
108:
109: public void exceptionThrown(Exception e) {
110: fireNotification(new ExceptionNotification(e));
111: logException(e);
112:
113: Throwable t = getExceptionType(e, RoutingException.class);
114: if (t != null) {
115: RoutingException re = (RoutingException) t;
116: handleRoutingException(re.getUmoMessage(),
117: re.getEndpoint(), e);
118: return;
119: }
120:
121: t = getExceptionType(e, MessagingException.class);
122: if (t != null) {
123: MessagingException me = (MessagingException) t;
124: handleMessagingException(me.getUmoMessage(), e);
125: return;
126: }
127:
128: t = getExceptionType(e, LifecycleException.class);
129: if (t != null) {
130: LifecycleException le = (LifecycleException) t;
131: handleLifecycleException(le.getComponent(), e);
132: if (RequestContext.getEventContext() != null) {
133: handleMessagingException(RequestContext
134: .getEventContext().getMessage(), e);
135: } else {
136: logger
137: .info("There is no current event available, routing Null message with the exception");
138: handleMessagingException(new DefaultMuleMessage(
139: NullPayload.getInstance()), e);
140: }
141: return;
142: }
143:
144: handleStandardException(e);
145: }
146:
147: protected Throwable getExceptionType(Throwable t,
148: Class exceptionType) {
149: while (t != null) {
150: if (exceptionType.isAssignableFrom(t.getClass())) {
151: return t;
152: }
153:
154: t = t.getCause();
155: }
156:
157: return null;
158: }
159:
160: /**
161: * The initialise method is call every time the Exception stategy is assigned to
162: * a service or connector. This implementation ensures that initialise is
163: * called only once. The actual initialisation code is contained in the
164: * <code>doInitialise()</code> method.
165: *
166: * @throws InitialisationException
167: */
168: public final synchronized LifecycleTransitionResult initialise()
169: throws InitialisationException {
170: if (!initialised.get()) {
171: doInitialise(muleContext);
172: initialised.set(true);
173: }
174: return LifecycleTransitionResult.OK;
175: }
176:
177: protected void doInitialise(MuleContext muleContext)
178: throws InitialisationException {
179: logger.info("Initialising exception listener: " + toString());
180: }
181:
182: /**
183: * If there is a current transaction this method will mark it for rollback This
184: * method should not be called if an event is routed from this exception handler
185: * to an endpoint that should take part in the current transaction
186: */
187: protected void markTransactionForRollback() {
188: Transaction tx = TransactionCoordination.getInstance()
189: .getTransaction();
190: try {
191: if (tx != null) {
192: tx.setRollbackOnly();
193: }
194: } catch (TransactionException e) {
195: logException(e);
196: }
197: }
198:
199: /**
200: * Routes the current exception to an error endpoint such as a Dead Letter Queue
201: * (jms) This method is only invoked if there is a MuleMessage available to
202: * dispatch. The message dispatched from this method will be an
203: * <code>ExceptionMessage</code> which contains the exception thrown the
204: * MuleMessage and any context information.
205: *
206: * @param message the MuleMessage being processed when the exception occurred
207: * @param failedEndpoint optional; the endpoint being dispatched or received on
208: * when the error occurred. This is NOT the endpoint that the message
209: * will be disptched on and is only supplied to this method for
210: * logging purposes
211: * @param t the exception thrown. This will be sent with the ExceptionMessage
212: * @see ExceptionMessage
213: */
214: protected void routeException(MuleMessage message,
215: ImmutableEndpoint failedEndpoint, Throwable t) {
216: OutboundEndpoint endpoint = getEndpoint(t);
217: if (endpoint != null) {
218: try {
219: logger.error("Message being processed is: "
220: + (message == null ? "null" : message
221: .toString()));
222: MuleEventContext ctx = RequestContext.getEventContext();
223: String component = "Unknown";
224: EndpointURI endpointUri = null;
225: if (ctx != null) {
226: if (ctx.getService() != null) {
227: component = ctx.getService().getName();
228: }
229: endpointUri = ctx.getEndpointURI();
230: } else if (failedEndpoint != null) {
231: endpointUri = failedEndpoint.getEndpointURI();
232: }
233: ExceptionMessage msg;
234: msg = new ExceptionMessage(
235: getErrorMessagePayload(message), t, component,
236: endpointUri);
237:
238: MuleMessage exceptionMessage;
239: if (ctx == null) {
240: exceptionMessage = new DefaultMuleMessage(msg);
241: } else {
242: exceptionMessage = new DefaultMuleMessage(msg, ctx
243: .getMessage());
244: }
245: MuleEvent exceptionEvent = new DefaultMuleEvent(
246: exceptionMessage, endpoint,
247: new DefaultMuleSession(exceptionMessage,
248: new MuleSessionHandler(), muleContext),
249: true);
250: exceptionEvent = RequestContext
251: .setEvent(exceptionEvent);
252: endpoint.send(exceptionEvent);
253:
254: if (logger.isDebugEnabled()) {
255: logger.debug("routed Exception message via "
256: + endpoint);
257: }
258:
259: } catch (MuleException e) {
260: logFatal(message, e);
261: }
262: } else {
263: markTransactionForRollback();
264: }
265: }
266:
267: protected Object getErrorMessagePayload(MuleMessage message) {
268: try {
269: return message.getPayloadAsString();
270: } catch (Exception e) {
271: logException(e);
272: logger
273: .info("Failed to read message payload as string, using raw payload");
274: return message.getPayload();
275: }
276: }
277:
278: /**
279: * Returns an endpoint for the given exception. ExceptionListeners can have
280: * multiple endpoints registered on them. This methods allows custom
281: * implementations to control which endpoint is used based on the exception
282: * thrown. This implementation simply returns the first endpoint in the list.
283: *
284: * @param t the exception thrown
285: * @return The endpoint used to dispatch an exception message on or null if there
286: * are no endpoints registered
287: */
288: protected OutboundEndpoint getEndpoint(Throwable t) {
289: if (endpoints.size() > 0) {
290: return (OutboundEndpoint) endpoints.get(0);
291: } else {
292: return null;
293: }
294: }
295:
296: /**
297: * Used to log the error passed into this Exception Listener
298: *
299: * @param t the exception thrown
300: */
301: protected void logException(Throwable t) {
302: MuleException umoe = ExceptionHelper.getRootMuleException(t);
303: if (umoe != null) {
304: logger.error(umoe.getDetailedMessage());
305: } else {
306: logger.error("Caught exception in Exception Strategy: "
307: + t.getMessage(), t);
308: }
309: }
310:
311: /**
312: * Logs a fatal error message to the logging system. This should be used mostly
313: * if an error occurs in the exception listener itself. This implementation logs
314: * the the message itself to the logs if it is not null
315: *
316: * @param message The MuleMessage currently being processed
317: * @param t the fatal exception to log
318: */
319: protected void logFatal(MuleMessage message, Throwable t) {
320: logger
321: .fatal(
322: "Failed to dispatch message to error queue after it failed to process. This may cause message loss."
323: + (message == null ? ""
324: : "Logging Message here: \n"
325: + message.toString()),
326: t);
327: }
328:
329: public boolean isInitialised() {
330: return initialised.get();
331: }
332:
333: public void dispose() {
334: // Template method
335: }
336:
337: /**
338: * Fires a server notification to all registered
339: * {@link org.mule.api.context.notification.listener.ExceptionNotificationListener}
340: * eventManager.
341: *
342: * @param notification the notification to fire.
343: */
344: protected void fireNotification(ExceptionNotification notification) {
345: if (muleContext != null) {
346: muleContext.fireNotification(notification);
347: }
348: }
349:
350: /**
351: * A messaging exception is thrown when an excpetion occurs during normal message
352: * processing. A <code>MessagingException</code> holds a reference to the
353: * current message that is passed into this method
354: *
355: * @param message the current message being processed
356: * @param e the top level exception thrown. This may be a Messaging exception or
357: * some wrapper exception
358: * @see MessagingException
359: */
360: public abstract void handleMessagingException(MuleMessage message,
361: Throwable e);
362:
363: /**
364: * A routing exception is thrown when an excpetion occurs during normal message
365: * processing A <code>RoutingException</code> holds a reference to the current
366: * message and te endpoint being routing to or from when the error occurred. Both
367: * are passed into this method
368: *
369: * @param message the current message being processed
370: * @param endpoint the endpoint being dispatched to or received from when the
371: * error occurred
372: * @param e the top level exception thrown. This may be a Messaging exception or
373: * some wrapper exception
374: * @see RoutingException
375: */
376: public abstract void handleRoutingException(MuleMessage message,
377: ImmutableEndpoint endpoint, Throwable e);
378:
379: /**
380: * DefaultLifecyclePhase exceptions are thrown when an error occurs during an object's
381: * lifecycle call such as start, stop or initialise. The exception contains a
382: * reference to the object that failed which can be used for more informative
383: * logging.
384: *
385: * @param service the object that failed during a lifecycle call
386: * @param e the top level exception thrown. This may or may not be the
387: * <code>LifecycleException</code> but a lifecycle exception will be
388: * present in the exception stack.
389: * @see LifecycleException
390: */
391: public abstract void handleLifecycleException(Object component,
392: Throwable e);
393:
394: /**
395: * A handler for all other exceptions
396: *
397: * @param e the top level exception thrown
398: */
399: public abstract void handleStandardException(Throwable e);
400: }
|