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: IdRepoException.java,v 1.8 2009/11/19 18:18:47 bhavnab Exp $
026 *
027 * Portions Copyrighted 2011-2015 ForgeRock AS.
028 */
029package com.sun.identity.idm;
030
031import com.sun.identity.shared.debug.Debug;
032import com.sun.identity.shared.locale.AMResourceBundleCache;
033import com.sun.identity.shared.locale.L10NMessage;
034import com.sun.identity.shared.locale.Locale;
035import java.text.MessageFormat;
036import java.util.ResourceBundle;
037import java.util.regex.Matcher;
038import java.util.regex.Pattern;
039
040import org.forgerock.opendj.ldap.ResultCode;
041
042/**
043 * The exception class whose instance is thrown if there is any error during the
044 * operation of objects of the <code>com.sun.identity.sms</code> package. This
045 * class maps the exception that occurred at a lower level to a high level
046 * error. Using the exception status code <code>getExceptionCode()</code> the
047 * errors are categorized as a <code>ABORT</code>, <code>RETRY</code>,
048 * <code>CONFIG_PROBLEM</code> or <code>LDAP_OP_FAILED</code> (typically a
049 * bug).
050 *
051 * @supported.all.api
052 */
053public class IdRepoException extends Exception implements L10NMessage {
054
055    // Static variable
056    private transient AMResourceBundleCache amCache = AMResourceBundleCache
057            .getInstance();
058
059    private transient Debug debug = AMIdentityRepository.debug;
060
061    private final static Pattern PATTERN = Pattern.compile("(.*)(?=: (.*?)uid=)|(.*)(?=(.*?)uid=)|(.*)");
062
063    // Instance variables
064    private String message;
065
066    private String bundleName;
067
068    private String errorCode;
069
070    private Object[] args;
071
072    private String ldapErrCode = null;
073
074    public IdRepoException() {
075    }
076
077    /**
078     * @param msg
079     *            The message provided by the object which is throwing the
080     *            exception
081     */
082    public IdRepoException(String msg) {
083        message = msg;
084    }
085
086    public IdRepoException(String msg, String errorCode) {
087        message = msg;
088        this.errorCode = errorCode;
089    }
090
091   /**
092     * This constructor is used to pass the localized error message At this
093     * level, the locale of the caller is not known and it is not possible to
094     * throw localized error message at this level. Instead this constructor
095     * provides Resource Bundle name ,error code and LDAP error code ( in case
096     * of LDAP related exception for correctly locating the
097     * error message. The default <code>getMessage()</code> will always return
098     * English messages only. This is in consistent with current JRE.
099     *
100     * @param rbName
101     *            Resource bundle Name to be used for getting localized error
102     *            message.
103     * @param errorCode
104     *            Key to resource bundle. You can use <code>ResourceBundle rb =
105     *        ResourceBunde.getBundle(rbName,locale);
106     *        String localizedStr = rb.getString(errorCode)</code>.
107     * @param ldapErrCode
108     *            ldap error code
109     * @param args
110     *            arguments to message. If it is not present pass the as null.
111     *  @deprecated
112     *            Passing in an ldapErrorCode as a String is not recommended, use the
113     *            OO ctor instead.
114     */
115    @Deprecated
116    public IdRepoException(String rbName, String errorCode, String ldapErrCode, Object[] args)
117    {
118        this.bundleName = rbName;
119        this.errorCode = errorCode;
120        this.ldapErrCode = ldapErrCode;
121        this.args = args;
122        this.message = getL10NMessage(java.util.Locale.ENGLISH);
123    }
124
125    /**
126     * This constructor is used to pass the localized error message At this
127     * level, the locale of the caller is not known and it is not possible to
128     * throw localized error message at this level. Instead this constructor
129     * provides Resource Bundle name ,error code and LDAP Result Code ( in case
130     * of LDAP related exception for correctly locating the
131     * error message. The default <code>getMessage()</code> will always return
132     * English messages only. This is in consistent with current JRE.
133     *
134     * @param rbName
135     *            Resource bundle Name to be used for getting localized error
136     *            message.
137     * @param errorCode
138     *            Key to resource bundle. You can use <code>ResourceBundle rb =
139     *        ResourceBunde.getBundle(rbName,locale);
140     *        String localizedStr = rb.getString(errorCode)</code>.
141     * @param ldapResultCode
142     *            ldap result code
143     * @param args
144     *            arguments to message. If it is not present pass the as null.
145     */
146    public IdRepoException(String rbName, String errorCode, ResultCode ldapResultCode, Object[] args) {
147        this.bundleName = rbName;
148        this.errorCode = errorCode;
149        this.ldapErrCode = String.valueOf(ldapResultCode.intValue());
150        this.args = args;
151        this.message = getL10NMessage(java.util.Locale.ENGLISH);
152    }
153
154    /**
155     * This constructor is used to pass the localized error message At this
156     * level, the locale of the caller is not known and it is not possible to
157     * throw localized error message at this level. Instead this constructor
158     * provides Resource Bundle name and error code for correctly locating the
159     * error message. The default <code>getMessage()</code> will always return
160     * English messages only. This is in consistent with current JRE.
161     * 
162     * @param rbName
163     *            Resource bundle Name to be used for getting localized error
164     *            message.
165     * @param errorCode
166     *            Key to resource bundle. You can use <code>ResourceBundle rb =
167     *        ResourceBunde.getBundle(rbName,locale);
168     *        String localizedStr = rb.getString(errorCode)</code>.
169     * @param args
170     *            arguments to message. If it is not present pass the as null.
171     */
172    public IdRepoException(String rbName, String errorCode, Object[] args) {
173        this.bundleName = rbName;
174        this.errorCode = errorCode;
175        this.args = args;
176        this.message = getL10NMessage(java.util.Locale.ENGLISH);
177    }
178
179    /**
180     * Returns a localized error message
181     * 
182     * @param locale
183     *            Uses the locale object to create the appropriate localized
184     *            error message
185     * @return localized error message.
186     * @see #IdRepoException(String, String, Object[])
187     */
188    public String getL10NMessage(java.util.Locale locale) {
189        String result = errorCode;
190        if (bundleName != null && locale != null) {
191            ResourceBundle bundle = amCache.getResBundle(bundleName, locale);
192            String mid = Locale.getString(bundle, errorCode, debug);
193            if (args == null || args.length == 0) {
194                result = mid;
195            } else {
196                result = MessageFormat.format(mid, args);
197            }
198        }
199        return result;
200    }
201
202    /**
203     * Returns <code>ResourceBundle</code> Name associated with this error
204     * message.
205     * 
206     * @return <code>ResourceBundle</code> name associated with this error
207     *         message.
208     * @see #IdRepoException(String, String, Object[])
209     */
210    public String getResourceBundleName() {
211        return bundleName;
212    }
213
214    /**
215     * Returns an int representation of <code>ldapErrCode</code>.
216     * This is to be used with ResultCode.valueOf() which will return a valid
217     * ResultCode object regardless of the return of this method.
218     *
219     * A null or invalid <code>ldapErrorCode</code> will return <code>-1</code>.
220     *
221     * @return an int representation of this exception's LDAP error code.
222     */
223    public int getLdapErrorIntCode() {
224        try {
225            return Integer.valueOf(ldapErrCode);
226        } catch (NumberFormatException nfe) {
227            return -1;
228        }
229    }
230
231    /**
232     * Returns error code associated with this error message.
233     * 
234     * @return Error code associated with this error message.
235     * @see #IdRepoException(String, String, Object[])
236     */
237    public String getErrorCode() {
238        return errorCode;
239    }
240
241     /**
242      * Returns the LDAP error code associated with this error message.
243      *
244      * @return Error code associated with this error message and null if
245      *      not caused by <code>LDAPException</code>.
246      * @see #IdRepoException(String, String, Object[])
247      *
248      * @deprecated
249      *     Use #getLdapErrorIntCode() instead. The ldap error code is always an int
250      */
251     @Deprecated
252     public String getLDAPErrorCode() {
253         return ldapErrCode;
254     }
255
256     /**
257      * Replace the LDAP error code associated with this error message.
258      *
259      * @see #IdRepoException(String, String, Object[])
260      */
261     public void setLDAPErrorCode(String errorCode) {
262         ldapErrCode = errorCode;
263     }
264
265    /**
266     * Returns arguments for formatting this error message.
267     * 
268     * @return arguments for formatting this error message. You need to use
269     *         <code>MessageFormat</code> class to format the message It can
270     *         be null.
271     * @see #IdRepoException(String, String, Object[])
272     */
273    public Object[] getMessageArgs() {
274        return args;
275    }
276
277    public String toString() {
278        StringBuilder buf = new StringBuilder();
279        String msg = message;
280        if (msg != null && msg.length() > 0) {
281            buf.append("Message:");
282            buf.append(msg);
283            buf.append("\n");
284        }
285        return buf.toString();
286    }
287
288    /**
289     * Returns the error message of this exception.
290     * 
291     * @return String representing the error message
292     */
293    public String getMessage() {
294        return message;
295    }
296
297    /**
298     * If this error is an instance of a LDAP Constraint Violated Error (LDAP code 313)
299     * attempts to return useful information about the error that occured without
300     * leaking additional information about the system to the calling user.
301     *
302     * If this error is not an instance of LDAP Constraint Violated Error, the message
303     * of the error is returned.
304     *
305     * @return a user-facing representation of this exception.
306     */
307    public String getConstraintViolationDetails() {
308        if (!IdRepoErrorCode.LDAP_EXCEPTION.equals(getErrorCode()) || args == null || args.length < 3) {
309            return getMessage();
310        }
311
312        String argMatch = (String) args[2];
313
314        if (argMatch != null) {
315            Matcher matcher = PATTERN.matcher(argMatch);
316
317            if (matcher.find()) {
318                return matcher.group(0).trim();
319            }
320        }
321
322        return "Cannot update attributes due to server's attribute constraints being violated.";
323    }
324}