| /* |
| |
| Derby - Class org.apache.derby.shared.common.error; |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to you under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| |
| package org.apache.derby.shared.common.error; |
| |
| import java.lang.StringBuilder; |
| import java.math.BigDecimal; |
| import java.sql.DataTruncation; |
| import java.sql.SQLException; |
| import java.sql.SQLFeatureNotSupportedException; |
| import java.sql.SQLDataException; |
| import java.sql.SQLIntegrityConstraintViolationException; |
| import java.sql.SQLInvalidAuthorizationSpecException; |
| import java.sql.SQLNonTransientConnectionException; |
| import java.sql.SQLSyntaxErrorException; |
| import java.sql.SQLTimeoutException; |
| import java.sql.SQLTransactionRollbackException; |
| import java.text.MessageFormat; |
| import java.util.Locale; |
| import java.util.MissingResourceException; |
| import java.util.ResourceBundle; |
| import org.apache.derby.shared.common.sanity.SanityManager; |
| import org.apache.derby.shared.common.reference.SQLState; |
| |
| |
| |
| |
| public class MessageUtils |
| { |
| private static final Locale EN = new Locale("en", "US"); |
| public static final String SQLERRMC_MESSAGE_DELIMITER = new String(new char[] {(char)20,(char)20,(char)20}); |
| /** |
| * Pointer to the application requester |
| * for the session being serviced. |
| */ |
| |
| public static final int DB2_JCC_MAX_EXCEPTION_PARAM_LENGTH = 2400; |
| |
| /** |
| * Delimiters for SQLERRMC values. |
| * The token delimiter value will be used to parse the MessageId from the |
| * SQLERRMC in MessageService.getLocalizedMessage and the MessageId will be |
| * used to retrive the localized message. If this delimiter value is changed |
| * please make sure to make appropriate changes in |
| * MessageService.getLocalizedMessage that gets called from |
| * SystemProcedures.SQLCAMESSAGE |
| * <code>SQLERRMC_TOKEN_DELIMITER</code> separates message argument tokens |
| */ |
| public static String SQLERRMC_TOKEN_DELIMITER = new String(new char[] {(char)20}); |
| |
| /** |
| * <code>SQLERRMC_PREFORMATTED_MESSAGE_DELIMITER</code>, When full message text is |
| * sent for severe errors. This value separates the messages. |
| */ |
| private static String SQLERRMC_PREFORMATTED_MESSAGE_DELIMITER = "::"; |
| |
| protected int supportedMessageParamLength() { |
| return DB2_JCC_MAX_EXCEPTION_PARAM_LENGTH; |
| } |
| |
| |
| /* |
| * Get messageId and arguments, messageId is necessary for us to look up |
| * localized message from property file. messageId was sent as the last |
| * token in the sqlerrmc. |
| * The last element of the returned array contains the MessageId field, that |
| * must be extracted by the caller funtion, to get an array of arguments. |
| */ |
| |
| public static Object[] getArgs(String sqlState, String sqlerrmc) |
| { |
| String messageId = sqlState; //use sqlState if we don't have messageId |
| Object[] arguments = null; |
| if (sqlerrmc != null && sqlerrmc.length() > 0) |
| { |
| char [] sqlerrmc_chars = sqlerrmc.toCharArray(); |
| int numArgs = 0, lastSepIdx = -1; // last separator index |
| for (int i = 0; i < sqlerrmc_chars.length; i++) |
| { |
| if (sqlerrmc_chars[i] == 20) // separator |
| { |
| numArgs++; |
| lastSepIdx = i; |
| } |
| } |
| if (numArgs == 0) |
| { |
| messageId = new String(sqlerrmc_chars); //no args, only messageId then |
| arguments = new Object[1]; |
| } |
| else |
| { |
| messageId = new String(sqlerrmc_chars, lastSepIdx+1, sqlerrmc_chars.length-lastSepIdx-1); |
| arguments = new Object[numArgs + 1]; |
| for (int start = 0, arg = 0, i = 0; i < lastSepIdx + 1; i++) |
| { |
| if (i == lastSepIdx || sqlerrmc_chars[i] == 20) // delimiter |
| { |
| arguments[arg++] = new String(sqlerrmc_chars, start, i - start); |
| start = i + 1; |
| } |
| } |
| } |
| arguments[arguments.length - 1] = messageId; |
| } |
| else |
| { |
| // messageId must be returned for all cases. |
| arguments = new Object[1]; |
| arguments[arguments.length - 1] = messageId; |
| } |
| |
| return arguments; |
| } |
| |
| public static String encodeMessageAndArgumentsAsSqlerrmc( |
| String messageId, |
| Object []args ) |
| { |
| String sqlerrmc = ""; |
| |
| // arguments are variable part of a message |
| |
| for (int i = 0; args != null && i < args.length; i++) { |
| sqlerrmc += args[i] + SQLERRMC_TOKEN_DELIMITER; |
| } |
| sqlerrmc += messageId; |
| |
| return sqlerrmc; |
| } |
| |
| public static String encodeExceptionAsSqlerrmc( SQLException se ) |
| { |
| // this could happen for instance if an SQLException was thrown |
| // from a stored procedure. |
| StringBuilder sb = new StringBuilder(); |
| sb.append(se.getLocalizedMessage()); |
| se = se.getNextException(); |
| if (se != null) { |
| sb.append(SQLERRMC_TOKEN_DELIMITER); |
| sb.append("SQLSTATE: ").append(se.getSQLState()); |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * Build the SQLERRMC for a {@code java.sql.DataTruncation} warning. |
| * Serialize all the fields of the {@code DataTruncation} instance in the |
| * order in which they appear in the parameter list of the constructor. |
| * |
| * @param dt the {@code DataTruncation} instance to serialize |
| * @return the SQLERRMC string with all fields of the warning |
| */ |
| private String buildDataTruncationSqlerrmc(DataTruncation dt) { |
| return dt.getIndex() + SQLERRMC_TOKEN_DELIMITER + |
| dt.getParameter() + SQLERRMC_TOKEN_DELIMITER + |
| dt.getRead() + SQLERRMC_TOKEN_DELIMITER + |
| dt.getDataSize() + SQLERRMC_TOKEN_DELIMITER + |
| dt.getTransferSize(); |
| } |
| |
| |
| |
| /** |
| * Build preformatted SQLException text |
| * for severe exceptions or SQLExceptions that are not Derby exceptions. |
| * Just send the message text localized to the server locale. |
| * |
| * @param se SQLException for which to build SQLERRMC |
| * @return preformated message text |
| * with messages separted by SQLERRMC_PREFORMATED_MESSAGE_DELIMITER |
| * |
| */ |
| private String buildPreformattedSqlerrmc(SQLException se) { |
| if (se == null) { |
| return ""; |
| } |
| |
| // String buffer to build up message |
| StringBuilder sb = new StringBuilder(); |
| sb.append(se.getLocalizedMessage()); |
| while ((se = se.getNextException()) != null) { |
| sb.append(SQLERRMC_PREFORMATTED_MESSAGE_DELIMITER); |
| sb.append("SQLSTATE: "); |
| sb.append(se.getSQLState()); |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * Hash function to split messages into 50 files based upon the message identifier |
| * or SQLState. We don't use String.hashCode() as it varies between releases and |
| * doesn't provide an even distribution across the 50 files. |
| */ |
| public static int hashString50(String key) { |
| int hash = 0; |
| int len = key.length(); |
| if (len > 5) |
| len = 5; |
| |
| for (int i = 0; i < len; i++) { |
| hash += key.charAt(i); |
| } |
| hash = hash % 50; |
| return hash; |
| } |
| |
| /** |
| * Method to use instead of ResourceBundle.getBundle(). This method acts like ResourceBundle. |
| * getBundle() but if the resource is not available in the requested locale, default locale |
| * or base class the one for en_US is returned. |
| */ |
| public static ResourceBundle getBundleWithEnDefault(String resource, Locale locale) { |
| |
| try { |
| return ResourceBundle.getBundle(resource, locale); |
| } catch (MissingResourceException mre) { |
| |
| // This covers the case where neither the |
| // requested locale or the default locale |
| // have a resource. |
| |
| return ResourceBundle.getBundle(resource, EN); |
| } |
| } |
| |
| /** |
| * |
| */ |
| public static ResourceBundle getBundleForLocale(Locale locale, String msgId) { |
| try { |
| return MessageUtils.getBundleWithEnDefault("org.apache.derby.loc.m"+hashString50(msgId), locale); |
| } catch (MissingResourceException mre) { |
| } |
| return null; |
| } |
| |
| public static String formatMessage(ResourceBundle bundle, String messageId, Object[] arguments, boolean lastChance) { |
| |
| if (arguments == null) |
| arguments = new Object[0]; |
| |
| if (bundle != null) { |
| |
| try { |
| messageId = bundle.getString(messageId); |
| |
| try { |
| return MessageFormat.format(messageId, arguments); |
| } |
| catch (IllegalArgumentException iae) { |
| } |
| catch (NullPointerException npe) { |
| // |
| //null arguments cause a NullPointerException. |
| //This improves reporting. |
| } |
| |
| } catch (MissingResourceException mre) { |
| // caller will try and handle the last chance |
| if (lastChance) |
| throw mre; |
| } |
| } |
| |
| if (messageId == null) |
| messageId = "UNKNOWN"; |
| |
| |
| StringBuffer sb = new StringBuffer(messageId); |
| |
| int len = arguments.length; |
| if (len > 0) |
| sb.append(" : "); |
| |
| for (int i=0; i < len; i++) { |
| // prepend a comma to all but the first |
| if (i > 0) |
| sb.append(", "); |
| |
| sb.append('['); |
| sb.append(i); |
| sb.append("] "); |
| if (arguments[i] == null) |
| sb.append("null"); |
| else |
| sb.append(arguments[i].toString()); |
| } |
| |
| |
| return sb.toString(); |
| } |
| |
| /** |
| * Method used by Derby Network Server to get localized message |
| * @param sqlcode sqlcode, not used. |
| * @param errmcLen sqlerrmc length |
| * @param sqlerrmc sql error message tokens, variable part of error message (ie., |
| * arguments) plus messageId, separated by separator. |
| * @param sqlerrp not used |
| * @param errd0 not used |
| * @param warn not used |
| * @param sqlState 5-char sql state |
| * @param file not used |
| * @param localeStr client locale in string |
| * @param msg OUTPUT parameter, localized error message |
| * @param rc OUTPUT parameter, return code -- 0 for success |
| */ |
| |
| public static void getLocalizedMessage(int sqlcode, short errmcLen, String sqlerrmc, |
| String sqlerrp, int errd0, int errd1, int errd2, |
| int errd3, int errd4, int errd5, String warn, |
| String sqlState, String file, String localeStr, |
| String[] msg, int[] rc) |
| { |
| //figure out client locale from input locale string |
| |
| int _pos1 = localeStr.indexOf("_"); // "_" position |
| |
| Locale locale = EN; //default locale |
| if (_pos1 != -1) |
| { |
| int _pos2 = localeStr.lastIndexOf("_"); |
| String language = localeStr.substring(0, _pos1); |
| if (_pos2 == _pos1) |
| { |
| String country = localeStr.substring(_pos1 + 1); |
| locale = new Locale(language, country); |
| } |
| else |
| { |
| String country = localeStr.substring(_pos1 + 1, _pos2); |
| String variant = localeStr.substring(_pos2 + 1); |
| locale = new Locale(language, country, variant); |
| } |
| } |
| |
| |
| String messageId = sqlState; //use sqlState if we don't have messageId |
| |
| Object[] args = getArgs(sqlState, sqlerrmc); |
| messageId = (String) args[args.length - 1]; |
| Object[] arguments = new Object[args.length - 1]; |
| |
| for (int i = 0; i < arguments.length; ++i) |
| { |
| arguments[i] = args[i]; |
| } |
| |
| try { |
| msg[0] = formatMessage(getBundleForLocale(locale, messageId), messageId, arguments, true); |
| rc[0] = 0; |
| return; |
| } catch (MissingResourceException mre) { |
| // message does not exist in the requested locale |
| // most likely it does exist in our fake base class _en, so try that. |
| } catch (ShutdownException se) { |
| } |
| msg[0] = formatMessage(getBundleForLocale(EN, messageId), messageId, arguments, false); |
| rc[0] = 0; |
| } |
| |
| /** |
| * Method used by Derby Network Server to get localized message |
| * @param locale locale |
| * @param messageId message id |
| * @param args message arguments |
| */ |
| public static String getLocalizedMessage(Locale locale, String messageId, Object [] args) |
| { |
| String locMsg = null; |
| |
| try { |
| locMsg = formatMessage(getBundleForLocale(locale, messageId), messageId, args, true); |
| return locMsg; |
| } catch (MissingResourceException mre) { |
| // message does not exist in the requested locale |
| // most likely it does exist in our fake base class _en, so try that. |
| } catch (ShutdownException se) { |
| } |
| locMsg = formatMessage(getBundleForLocale(EN, messageId), messageId, args, false); |
| return locMsg; |
| } |
| } |