up3PortalException

This page exists to document the PortalException in uPortal 3.

Design goals

  • Internationalizable exception messages
  • Exceptions which characterize the specific exceptional circumstance
  • Retain flexibility to allow developers to use arbitrary (non-internationalizable) message strings where necessary
  • Allow IProblemsManager to be notified of all IPortalExceptions thrown.

Overview

Scope

Subclasses of AbstractPortalException and AbstractUndeclaredPortalException in uPortal 3 are intended to be used in code that is part of the core uPortal 3 deliverable or is part of a module written to a uP 3 plugin API and expected to be plugged in as if it were part of the uPortal 3 deliverable. Thus, these exceptions are appropriate for use in MyLayoutManager. They are not intended for use in, say, a custom JSR 168 portlet that you'd like to use with uPortal but which isn't part of uPortal itself.

On translating from other types of exceptions

Libraries we use generate exceptions, especially runtime exceptions. It is usually neither necessary nor desirable to translate from other RuntimeExceptions to uPortal-specific exceptions. That is, often a component either succeeds or it fails, and if it fails, programmatically, our recovery is the same no matter how it failed (NullPointerExceptoin'ed out, overflowed the stack via infinite recursion, the database was down, whatever). We care deeply about characterizing the exception in morbid detail only so that a developer can pick up the trail on the issue later. So we need meaningful log messages and exception messages, but code probably shouldn't be catching IllegalArgumentException particularly without catching other Exceptions.

Parameterizing

uPortal 3 core exceptions expose constructor parameters allowing a developer to characterize what is different about this particular exceptional instance. The exceptions have default message templates. The parameters fill in the template to produce the exception message. A deployer wishing to plug in localized versions of these templates (for the desired language) injects a Spring MessageSource into AbstractPortalException and AbstractUndeclaredPortalException.

Implementation

AbstractPortalException

AbstractPortalException exposes two static setter methods for injecting the localized MessageSource from which to obtain exception message templates and the IProblemsManager to notify of instantiated AbstractPortalExceptions.

Static setter methods in AbstractPortalException
    /**
     * Set the problems manager to which PortalExceptions
     * should report themselves.
     * @param problemsManager - a manager to which to report insantiated PortalExceptions.
     */
    public static final void setProblemsManager(IProblemsManager problemsManager) {
        AbstractPortalException.problemsManager = problemsManager;
    }
    
    /**
     * Set the source from which to obtain message templates.
     * @param newSource - messageSource from which to obtain message templates.
     */
    public static final void setMessageSource(MessageSource newSource){
        AbstractPortalException.messageSource = newSource;
    }

The AbstractPortalException constructor reports the exception to the configured IProblemsManager instance. IProblemsManager implementations must be aware that the reference sent here is to a not-yet-fully-instantiated exception. Still, this approach allows a registry of the most recently generated exceptions, for instance.

Getting the localized exception message:

This is the core of the AbstractPortalException implementation.

AbstractPortalException getLocalizedMessage()

    public final String getLocalizedMessage(){
        
    	String[] parameters = getMessageParameters();
    	
    	// first, produce a default message based on the template
    	String defaultMessage = MessageFormat.format(getDefaultMessageTemplate(), parameters);
    	
    	// now, attempt to use our message source, specifying the
    	// default message we just computed as our fallback.
        String messageCode = 
            getClass().getName() + getMessageKeyExtension();
        Locale locale = Locale.getDefault();
        return messageSource.getMessage(messageCode, parameters, 
                defaultMessage, locale);
    }

Here we invoke the abstract method getMessageParameters() to get an array of String parameters to our exception message. We invoke the abstract method getDefaultMessageTemplate() to get the English-language standard template into which we'll insert the parameters to generate the default message. We do this integration using MessageFormat. Now, armed with this default version of the message, we give our configured MessageSource an opportunity to override this default with a localized version of the message. They messageCode key we give it is the name of the exception class followed by an optional suffix. The suffix will be used in the case where a particular exception implementation has associated with it more than one message template (usually selected depending upon what arguments were passed to the exception constructor).

Similar to AbstractPortalException is AbstractUndeclaredPortalException, a RuntimeException version.

PortalException

PortalException is the simplest example of a concrete class extending AbstractPortalException.

PortalException
/**
 * Try to avoid using this base portal exception class.
 * Instead of using this class, you are encouraged to use more specific
 * exceptions which extend AbstractPortalException 
 * (no need to extend this (final) class).
 * Using this class opts you out of the parameterized template messages and
 * internationalization / localization support provided by the core abstract 
 * exceptions.
 * @author andrew.petro@yale.edu
 * @version $Revision: 1.3 $ $Date: 2004/12/14 02:41:54 $
 */
public final class PortalException extends AbstractPortalException  {

    /**
     * A default message template which simply displays the message
     * passed at construction.
     */
    private static final String DEFAULT_MESSAGE = "{0}";
    
    private static final String DEFAULT_NO_MESSAGE = "No message was specified when this instance was instantiated.";
    
    /**
     * The message, if any, specified when this was instantiated.
     */
    private String message;
    
    /**
     * Generic constructor.
     * Instead of calling this directly, throw a more specific exception
     * with an appropriate parameterized message.
     * @deprecated - wouldn't you rather throw something with at least a message?
     */
    public PortalException() {
        super();
    }

    /**
     * Generic constructor that takes a message.
     * Instead of calling this directly, throw a more specific exception
     * with an appropriate parameterized message.
     * @param message - message describing the exceptional condition
     */
    public PortalException(String message) {
        super();
        this.message = message;
    }

    /**
     * Generic constructor that takes a Throwable cause.
     * Instead of calling this directly, use a more specific
     * exception with an appropriate parameterized message.
     * @param cause - the cause of the problem.
     */
    public PortalException(Throwable cause) {
        super(cause);
        this.message = cause.getMessage();
    }

    /**
     * Generic constructor that takes a message and cause.
     * Instead of calling this directly, use a more specific
     * exception with an appropriate parameterized message.
     * @param message - description of exceptional condition
     * @param cause - underlying cause of exceptional condition.
     */
    public PortalException(String message, Throwable cause) {
        super(cause);
        this.message = message;
    }

    public String getDefaultMessageTemplate() {
        if (this.message == null)
            return PortalException.DEFAULT_NO_MESSAGE;
        return PortalException.DEFAULT_MESSAGE;
    }

    public String[] getMessageParameters() {
        String[] params = new String[1];
        params[0] = this.message;
        return params;
    }
    
    public String getMessageKeySuffix(){
        if (this.message == null){
            return "NO_MESSAGE";
        }
        return "";
        
    }

}

PortalException is the implementation of more traditional free-form untemplated exception messages. The developer specifies any exception message when the exception is thrown and that message is not further localizable.

Here we implement getMessageKeySuffix() to return a suffix in the special case where no message was specified as a constructor argument. The suffix returned depends upon how the PortalException was constructed.

getMessageParameters returns the message provided at construction.

So, when the abstract superclass is asked to getLocalizedMessage(), it will obtain a default message template that either directs that the parameter is the whole message or provides the fallback about no message being specified when the PortalException was instantiated. getLocalizedMessage() will obtain one String as the message parameter to the template. It integrates that one string (the message provided when the PortalException was instantiated) into the template to produce that message.

This is the degenerate case in which we don't take advantage of much of what AbstractPortalException offers: we don't use the templating facility and we don't use the localization facility.

RenderingException

RenderingException demonstrates using more of the features provided by AbstractPortalException.

RenderingException

/**
 * RenderingException description.
 *
 * @author Peter Kharchenko: pkharchenko at unicon.net
 * @version $Revision: 1.3 $
 */
public class RenderingException extends AbstractPortalException {

    /**
     * Stage of the rendering process where problem was encountered
     */
    private String stageName;
    
    /**
     * Default message for a rendering exception.
     */
    private static final String DEFAULT_MESSAGE = "Rendering problem occurred at the following stage: [UPC:{0}].";


    public RenderingException(String stage) {
        super();
        this.stageName=stage;
    }
    
    /**
     * Create a new rendering exception, passing the original cause and stage description
     * @param cause immediate cause
     * @param stage rendering stage description
     */
    public RenderingException(Throwable cause, String stage) {
        super(cause);
        this.stageName=stage;
    }

    public String getDefaultMessageTemplate() {
        return DEFAULT_MESSAGE;
    }

    public String[] getMessageParameters() {
        String[] params = new String[1];
        params[0] = this.stageName;
        return params;
    }

}

RenderingException has one parameter, the name of the rending stage in which the exceptional circumstance was encountered. The default message provides a template which includes the text common across all instances of RenderingException ("Rendering problem occurred at the following stage; ") and templates in the name of the stage that was the problem for this particular instance.

The key for this message template is "org.jasig.portal.rendering". Injecting a MessageSource with that key declared into AbstractPortalException allows provding a localized version of this template message.