001/**
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved
005 *
006 * The contents of this file are subject to the terms
007 * of the Common Development and Distribution License
008 * (the License). You may not use this file except in
009 * compliance with the License.
010 *
011 * You can obtain a copy of the License at
012 * https://opensso.dev.java.net/public/CDDLv1.0.html or
013 * opensso/legal/CDDLv1.0.txt
014 * See the License for the specific language governing
015 * permission and limitations under the License.
016 *
017 * When distributing Covered Code, include this CDDL
018 * Header Notice in each file and include the License file
019 * at opensso/legal/CDDLv1.0.txt.
020 * If applicable, add the following below the CDDL Header,
021 * with the fields enclosed by brackets [] replaced by
022 * your own identifying information:
023 * "Portions Copyrighted [year] [name of copyright owner]"
024 *
025 * $Id: SMSException.java,v 1.7 2009/01/28 05:35:03 ww203982 Exp $
026 *
027 */
028
029/*
030 * Portions Copyrighted [2011] [ForgeRock AS]
031 */
032package com.sun.identity.sm;
033
034import com.sun.identity.authentication.internal.InvalidAuthContextException;
035import com.sun.identity.shared.debug.Debug;
036import com.sun.identity.shared.locale.AMResourceBundleCache;
037import com.sun.identity.shared.locale.L10NMessage;
038import com.sun.identity.shared.locale.Locale;
039import com.iplanet.am.sdk.AMException;
040import com.iplanet.services.ldap.LDAPServiceException;
041import com.iplanet.services.ldap.event.EventException;
042import com.iplanet.services.util.XMLException;
043import com.iplanet.sso.SSOException;
044import com.iplanet.ums.IUMSConstants;
045import java.io.PrintWriter;
046import java.io.StringWriter;
047import java.text.MessageFormat;
048import java.util.ResourceBundle;
049import com.sun.identity.shared.ldap.LDAPException;
050
051/**
052 * The exception class whose instance is thrown if there is any error during the
053 * operation of objects of the <code>com.sun.identity.sms</code> package. This
054 * class maps the exception that occurred at a lower level to a high level
055 * error. Using the exception status code <code>getExceptionCode()</code> the
056 * errors are categorized as a <code>ABORT</code>, <code>RETRY</code>,
057 * <code>CONFIG_PROBLEM</code> or <code>LDAP_OP_FAILED</code> (typically a
058 * bug).
059 *
060 * @supported.all.api
061 */
062public class SMSException extends Exception implements L10NMessage {
063
064    // Static variable
065    transient AMResourceBundleCache amCache = AMResourceBundleCache
066            .getInstance();
067
068    transient Debug debug = Debug.getInstance(IUMSConstants.UMS_DEBUG);
069
070    // Instance variables
071    private int exceptionStatus = STATUS_NONE;
072
073    private Throwable rootCause;
074
075    private String message;
076
077    private String bundleName = IUMSConstants.UMS_BUNDLE_NAME;
078
079    private String errorCode;
080
081    private Object[] args;
082
083    /**
084     * Default constructor for <code> SMSException </code>
085     */
086    public SMSException() {
087        super();
088        exceptionStatus = STATUS_NONE;
089    }
090
091    /**
092     * @param status
093     *            The exception status code.
094     * @param errorCode
095     *            Key to resource bundle.
096     */
097    public SMSException(int status, String errorCode) {
098        super();
099        exceptionStatus = status;
100        this.errorCode = errorCode;
101        this.message = getL10NMessage(java.util.Locale.ENGLISH);
102    }
103
104    /**
105     * @param status
106     *            The Exception status code.
107     * @param exMessage
108     *            The message provided by the object which is throwing the
109     *            exception
110     * @param errorCode
111     *            Key to resource bundle.
112     */
113    public SMSException(int status, String exMessage, String errorCode) {
114        exceptionStatus = status;
115        this.errorCode = errorCode;
116        this.message = exMessage + ": " +
117            getL10NMessage(java.util.Locale.ENGLISH);
118    }
119
120    /**
121     * @param msg
122     *            The message provided by the object which is throwing the
123     *            exception
124     */
125    public SMSException(String msg) {
126        exceptionStatus = STATUS_NONE;
127        this.message = msg;
128    }
129
130    /**
131     * @param msg
132     *            The message provided by the object which is throwing the
133     *            exception
134     * @param errorCode
135     *            Key to resource bundle.
136     */
137    public SMSException(String msg, String errorCode) {
138        exceptionStatus = STATUS_NONE;
139        this.errorCode = errorCode;
140        this.message = msg + ": " + getL10NMessage(java.util.Locale.ENGLISH);
141    }
142
143    /**
144     * Constructs an <code>SMSException</code>.
145     * 
146     * @param t
147     *            The <code>Throwable</code> object provided by the object
148     *            which is throwing the exception
149     * @param errorCode
150     *            Key to resource bundle.
151     */
152    public SMSException(Throwable t, String errorCode) {
153        // super(t); (can be used with JDK 1.4 and higher)
154        rootCause = t;
155        this.errorCode = errorCode;
156        this.message = getL10NMessage(java.util.Locale.ENGLISH);
157        exceptionMapper();
158    }
159
160    /**
161     * Constructs an <code>SMSException</code>.
162     * 
163     * @param message
164     *            exception message.
165     * @param t
166     *            The <code>Throwable</code> object provided by the object
167     *            which is throwing the exception.
168     * @param errorCode
169     *            Key to resource bundle.
170     */
171    public SMSException(String message, Throwable t, String errorCode) {
172        // super(message, t); (can be used with JDK 1.4 and higher)
173        rootCause = t;
174        this.errorCode = errorCode;
175        this.message = message + ": " +
176            getL10NMessage(java.util.Locale.ENGLISH);
177        exceptionMapper();
178    }
179
180    /**
181     * Constructs an <code>SMSException</code>.
182     * 
183     * @param rbName
184     *            Resource bundle Name to be used for getting localized error
185     *            message.
186     * @param message
187     *            exception message.
188     * @param t
189     *            The <code>Throwable</code> object provided by the object
190     *            which is throwing the exception.
191     * @param errorCode
192     *            Key to resource bundle.
193     */
194    public SMSException(String rbName, String message, Throwable t,
195            String errorCode) {
196        // super(message, t); (can be used with JDK 1.4 and higher)
197        rootCause = t;
198        this.errorCode = errorCode;
199        this.bundleName = rbName;
200        this.message = message + ": " +
201            getL10NMessage(java.util.Locale.ENGLISH);
202        if (rootCause != null && !(rootCause instanceof AMException)) {
203            exceptionMapper();
204        }
205    }
206
207    /**
208     * This constructor is used to pass the localized error message At this
209     * level, the locale of the caller is not known and it is not possible to
210     * throw localized error message at this level. Instead this constructor
211     * provides Resource Bundle name and error code for correctly locating the
212     * error message. The default <code>getMessage()</code> will always return
213     * English messages only. This is in consistent with current JRE.
214     * 
215     * @param rbName
216     *            Resource bundle Name to be used for getting localized error
217     *            message.
218     * @param errorCode
219     *            Key to resource bundle. You can use <code>ResourceBundle rb =
220     *        ResourceBunde.getBundle(rbName,locale);
221     *        String localizedStr = rb.getString(errorCode)</code>.
222     * @param args
223     *            arguments to message. If it is not present pass the as null.
224     */
225    public SMSException(String rbName, String errorCode, Object[] args) {
226        exceptionStatus = STATUS_NONE;
227        this.bundleName = rbName;
228        this.errorCode = errorCode;
229        this.args = args;
230        this.message = getL10NMessage(java.util.Locale.ENGLISH);
231    }
232
233    /**
234     * Returns a localized error message
235     * 
236     * @param locale
237     *            Uses the locale object to create the appropriate localized
238     *            error message
239     * @return localized error message.
240     * @see #SMSException(String, String, Object[])
241     */
242    public String getL10NMessage(java.util.Locale locale) {
243        String result = errorCode;
244        if (bundleName != null && locale != null) {
245            ResourceBundle bundle = amCache.getResBundle(bundleName, locale);
246            String mid = Locale.getString(bundle, errorCode, debug);
247            if (args == null || args.length == 0) {
248                result = mid;
249            } else {
250                result = MessageFormat.format(mid, args);
251            }
252        }
253        return result;
254    }
255
256    /**
257     * Returns <code>ResourceBundle</code> Name associated with this error
258     * message.
259     * 
260     * @return <code>ResourceBundle</code> name associated with this error
261     *         message.
262     * @see #SMSException(String, String, Object[])
263     */
264    public String getResourceBundleName() {
265        return bundleName;
266    }
267
268    /**
269     * Returns error code associated with this error message.
270     * 
271     * @return Error code associated with this error message.
272     * @see #SMSException(String, String, Object[])
273     */
274    public String getErrorCode() {
275        return errorCode;
276    }
277
278    /**
279     * Returns arguments for formatting this error message.
280     * 
281     * @return arguments for formatting this error message. You need to use
282     *         <code>MessageFormat</code> class to format the message It can
283     *         be null.
284     * @see #SMSException(String, String, Object[])
285     */
286    public Object[] getMessageArgs() {
287        return args;
288    }
289
290    /**
291     * Returns the status code for this exception.
292     * 
293     * @return Integer representing the exception status code
294     */
295    public int getExceptionCode() {
296        return exceptionStatus;
297    }
298
299    /**
300     * The this package can set the exception code.
301     * 
302     * @param status
303     *            The exception status code.
304     */
305    void setExceptionCode(int status) {
306        exceptionStatus = status;
307    }
308
309    public String toString() {
310        StringBuilder buf = new StringBuilder();
311        if (exceptionStatus != -1) {
312            buf.append("SMSException Exception Code:");
313            buf.append(exceptionStatus);
314            buf.append('\n');
315        }
316        String msg = message;
317        if (msg != null && msg.length() > 0) {
318            buf.append("Message:");
319            buf.append(msg);
320            buf.append("\n");
321        }
322
323        if (rootCause != null) {
324            buf.append("--------------------------------------------------\n");
325            buf.append("The lower level exception message\n");
326            buf.append(rootCause.getMessage());
327            buf.append('\n');
328            buf.append("The lower level exception:\n");
329            StringWriter sw = new StringWriter(100);
330            rootCause.printStackTrace(new PrintWriter(sw));
331            buf.append(sw.toString());
332            buf.append('\n');
333        }
334        return buf.toString();
335    }
336
337    /**
338     * Returns the error message of this exception.
339     * 
340     * @return String representing the error message
341     */
342    public String getMessage() {
343        return message;
344    }
345
346    private String getString(String msgID) {
347        errorCode = msgID;
348        ResourceBundle bundle = null;
349        if (bundleName != null) {
350            bundle = amCache.getResBundle(bundleName, java.util.Locale.ENGLISH);
351        }
352        return (Locale.getString(bundle, msgID, debug));
353    }
354
355    private void exceptionMapper() {
356        if (rootCause == null) {
357            return;
358        }
359        if (rootCause instanceof LDAPException) {
360            message = mapLDAPException();
361        } else if (rootCause instanceof LDAPServiceException) {
362            // do nothing
363        } else if (rootCause instanceof EventException) {
364            exceptionStatus = STATUS_ABORT;
365            message = getString(IUMSConstants.SMS_EVENT_NOTIFICATION_FAILED);
366        } else if (rootCause instanceof XMLException) {
367            exceptionStatus = STATUS_ABORT;
368            message = getString(IUMSConstants.SMS_XML_PARSER_EXCEPTION);
369        } else if (rootCause instanceof InvalidAuthContextException) {
370            message = getString(IUMSConstants.SMS_AUTHENTICATION_ERROR);
371            exceptionStatus = STATUS_ABORT;
372        } else if (rootCause instanceof SSOException) {
373            message = getString(IUMSConstants.SMS_AUTHENTICATION_ERROR);
374            exceptionStatus = STATUS_ABORT;
375        } else {
376            message = getString(IUMSConstants.SMS_UNKNOWN_EXCEPTION_OCCURRED);
377            exceptionStatus = STATUS_UNKNOWN_EXCEPTION;
378        }
379    }
380
381    private String mapLDAPException() {
382        int resultCode = ((LDAPException) rootCause).getLDAPResultCode();
383
384        String message = null;
385
386        switch (resultCode) {
387        // ////////////////////////////////
388        // Errors that need to be handled
389        // ////////////////////////////////
390
391        // Helpless errors
392        // All errors are helpless situations
393        // but some are more helpless than the others.
394        // These errors are either problems in connection
395        // or configuration. So, some can be retired and
396        // some are already busted.
397        case LDAPException.SERVER_DOWN:
398        case LDAPException.OTHER:
399            message = getString(IUMSConstants.SMS_SERVER_DOWN);
400            exceptionStatus = STATUS_RETRY;
401            break;
402        case LDAPException.LDAP_NOT_SUPPORTED:
403            message = getString(IUMSConstants.SMS_LDAP_NOT_SUPPORTED);
404            exceptionStatus = STATUS_ABORT;
405            break;
406        case LDAPException.BUSY:
407            message = getString(IUMSConstants.SMS_LDAP_SERVER_BUSY);
408            exceptionStatus = STATUS_RETRY;
409            break;
410
411        case LDAPException.INVALID_CREDENTIALS:
412            message = getString("INVALID_CREDENTIALS");
413            exceptionStatus = STATUS_CONFIG_PROBLEM;
414            break;
415
416        // Application must show exactly what is happening
417        case LDAPException.NO_SUCH_OBJECT:
418            message = getString(IUMSConstants.SMS_NO_SUCH_OBJECT);
419            exceptionStatus = STATUS_LDAP_OP_FAILED;
420            break;
421
422        case LDAPException.INSUFFICIENT_ACCESS_RIGHTS:
423            message = getString(IUMSConstants.SMS_INSUFFICIENT_ACCESS_RIGHTS);
424            exceptionStatus = STATUS_NO_PERMISSION;
425            break;
426
427        case LDAPException.ADMIN_LIMIT_EXCEEDED:
428            message = getString(IUMSConstants.SMS_ADMIN_LIMIT_EXCEEDED);
429            exceptionStatus = STATUS_ABORT;
430            break;
431
432        case LDAPException.TIME_LIMIT_EXCEEDED:
433            message = getString(IUMSConstants.SMS_TIME_LIMIT_EXCEEDED);
434            exceptionStatus = STATUS_ABORT;
435            break;
436
437        case LDAPException.REFERRAL:
438            message = getString(IUMSConstants.SMS_LDAP_REFERRAL_EXCEPTION);
439            exceptionStatus = STATUS_CONFIG_PROBLEM;
440            break;
441
442        // We screwed up with something
443        case LDAPException.OBJECT_CLASS_VIOLATION:
444        case LDAPException.NAMING_VIOLATION:
445        case LDAPException.CONSTRAINT_VIOLATION:
446        case LDAPException.INVALID_DN_SYNTAX:
447        case LDAPException.ENTRY_ALREADY_EXISTS:
448        case LDAPException.ATTRIBUTE_OR_VALUE_EXISTS:
449        case LDAPException.PROTOCOL_ERROR:
450        case LDAPException.UNDEFINED_ATTRIBUTE_TYPE:
451            SMSEntry.debug.error(rootCause.toString());
452            message = getString(IUMSConstants.SMS_LDAP_OPERATION_FAILED);
453            exceptionStatus = STATUS_LDAP_OP_FAILED;
454            break;
455
456        // Exception code that means logical operation.
457        case LDAPException.COMPARE_TRUE:
458        case LDAPException.COMPARE_FALSE:
459        case LDAPException.LDAP_PARTIAL_RESULTS:
460            exceptionStatus = STATUS_QUO_ANTE;
461            break;
462
463        default:
464            message = getString(IUMSConstants.SMS_UNEXPECTED_LDAP_EXCEPTION);
465            exceptionStatus = STATUS_UNKNOWN_EXCEPTION;
466        }
467        return message;
468    }
469
470    // Error codes
471    /** No status code is set */
472    public static int STATUS_NONE = -1;
473
474    /** Retry connection to data store */
475    public static int STATUS_RETRY = 0;
476
477    /** Repeated retry to data store failed */
478    public static int STATUS_REPEATEDLY_FAILED = 0;
479
480    /** Status to abort operation */
481    public static int STATUS_ABORT = 1;
482
483    /**
484     * If root LDAP cause is <code>LDAP_PARTIAL_RESULTS </code> then this
485     * status is set
486     */
487    public static int STATUS_QUO_ANTE = 2;
488
489    /**
490     * If root LDAP cause is an LDAP exception with one of the following error
491     * codes then this status is set.
492     * <p>
493     * 
494     * <PRE>
495     * 
496     * NO_SUCH_OBJECT OBJECT_CLASS_VIOLATION NAMING_VIOLATION
497     * CONSTRAINT_VIOLATION INVALID_DN_SYNTAX ENTRY_ALREADY_EXISTS
498     * ATTRIBUTE_OR_VALUE_EXISTS PROTOCOL_ERROR UNDEFINED_ATTRIBUTE_TYPE
499     * 
500     * </PRE>
501     */
502
503    public static int STATUS_LDAP_OP_FAILED = 3;
504
505    /**
506     * If the root LDAP exception is <code> INVALID_CREDENTIALS </code> or
507     * <code> REFERRAL </code> then this status is set
508     */
509    public static int STATUS_CONFIG_PROBLEM = 4;
510
511    /** If root cause is other than any of those listed in other status codes */
512    public static int STATUS_UNKNOWN_EXCEPTION = 5;
513
514    /** If the root LDAP cause is <code> INSUFFICIENT_ACCESS_RIGHTS </code> */
515    public static int STATUS_NO_PERMISSION = 8;
516
517    /** the operation is not allowed. */
518    public static int STATUS_NOT_ALLOW = 9;
519
520}