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