001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.dbcp2; 018 019import java.sql.Connection; 020import java.sql.ResultSet; 021import java.sql.Statement; 022import java.text.MessageFormat; 023import java.time.Duration; 024import java.time.Instant; 025import java.util.Collection; 026import java.util.HashSet; 027import java.util.Properties; 028import java.util.ResourceBundle; 029import java.util.Set; 030import java.util.function.Consumer; 031 032import org.apache.commons.pool2.PooledObject; 033 034/** 035 * Utility methods. 036 * 037 * @since 2.0 038 */ 039public final class Utils { 040 041 private static final ResourceBundle messages = ResourceBundle 042 .getBundle(Utils.class.getPackage().getName() + ".LocalStrings"); 043 044 /** 045 * Whether the security manager is enabled. 046 * 047 * @deprecated No replacement. 048 */ 049 @Deprecated 050 public static final boolean IS_SECURITY_ENABLED = isSecurityEnabled(); 051 052 /** 053 * Any SQL State starting with this value is considered a fatal disconnect. 054 */ 055 public static final String DISCONNECTION_SQL_CODE_PREFIX = "08"; 056 057 /** 058 * SQL codes of fatal connection errors. 059 * <ul> 060 * <li>57P01 (Admin shutdown)</li> 061 * <li>57P02 (Crash shutdown)</li> 062 * <li>57P03 (Cannot connect now)</li> 063 * <li>01002 (SQL92 disconnect error)</li> 064 * <li>JZ0C0 (Sybase disconnect error)</li> 065 * <li>JZ0C1 (Sybase disconnect error)</li> 066 * </ul> 067 * @deprecated Use {@link #getDisconnectionSqlCodes()}. 068 */ 069 @Deprecated 070 public static final Set<String> DISCONNECTION_SQL_CODES; 071 072 static final ResultSet[] EMPTY_RESULT_SET_ARRAY = {}; 073 074 static final String[] EMPTY_STRING_ARRAY = {}; 075 static { 076 DISCONNECTION_SQL_CODES = new HashSet<>(); 077 DISCONNECTION_SQL_CODES.add("57P01"); // Admin shutdown 078 DISCONNECTION_SQL_CODES.add("57P02"); // Crash shutdown 079 DISCONNECTION_SQL_CODES.add("57P03"); // Cannot connect now 080 DISCONNECTION_SQL_CODES.add("01002"); // SQL92 disconnect error 081 DISCONNECTION_SQL_CODES.add("JZ0C0"); // Sybase disconnect error 082 DISCONNECTION_SQL_CODES.add("JZ0C1"); // Sybase disconnect error 083 } 084 085 /** 086 * Checks for conflicts between two collections. 087 * <p> 088 * If any overlap is found between the two provided collections, an {@link IllegalArgumentException} is thrown. 089 * </p> 090 * 091 * @param codes1 The first collection of SQL state codes. 092 * @param codes2 The second collection of SQL state codes. 093 * @throws IllegalArgumentException if any codes overlap between the two collections. 094 * @since 2.13.0 095 */ 096 static void checkSqlCodes(final Collection<String> codes1, final Collection<String> codes2) { 097 if (codes1 != null && codes2 != null) { 098 final Set<String> test = new HashSet<>(codes1); 099 test.retainAll(codes2); 100 if (!test.isEmpty()) { 101 throw new IllegalArgumentException(test + " cannot be in both disconnectionSqlCodes and disconnectionIgnoreSqlCodes."); 102 } 103 } 104 } 105 106 /** 107 * Clones the given char[] if not null. 108 * 109 * @param value may be null. 110 * @return a cloned char[] or null. 111 */ 112 public static char[] clone(final char[] value) { 113 return value == null ? null : value.clone(); 114 } 115 116 /** 117 * Clones the given {@link Properties} without the standard "user" or "password" entries. 118 * 119 * @param properties may be null 120 * @return a clone of the input without the standard "user" or "password" entries. 121 * @since 2.8.0 122 */ 123 public static Properties cloneWithoutCredentials(final Properties properties) { 124 if (properties != null) { 125 final Properties temp = (Properties) properties.clone(); 126 temp.remove(Constants.KEY_USER); 127 temp.remove(Constants.KEY_PASSWORD); 128 return temp; 129 } 130 return properties; 131 } 132 133 /** 134 * Closes the given {@link AutoCloseable} and if an exception is caught, then calls {@code exceptionHandler}. 135 * 136 * @param autoCloseable The resource to close. 137 * @param exceptionHandler Consumes exception thrown closing this resource. 138 * @since 2.10.0 139 */ 140 public static void close(final AutoCloseable autoCloseable, final Consumer<Exception> exceptionHandler) { 141 if (autoCloseable != null) { 142 try { 143 autoCloseable.close(); 144 } catch (final Exception e) { 145 if (exceptionHandler != null) { 146 exceptionHandler.accept(e); 147 } 148 } 149 } 150 } 151 152 /** 153 * Closes the AutoCloseable (which may be null). 154 * 155 * @param autoCloseable an AutoCloseable, may be {@code null} 156 * @since 2.6.0 157 */ 158 public static void closeQuietly(final AutoCloseable autoCloseable) { 159 close(autoCloseable, null); 160 } 161 162 /** 163 * Closes the Connection (which may be null). 164 * 165 * @param connection a Connection, may be {@code null} 166 * @deprecated Use {@link #closeQuietly(AutoCloseable)}. 167 */ 168 @Deprecated 169 public static void closeQuietly(final Connection connection) { 170 closeQuietly((AutoCloseable) connection); 171 } 172 173 /** 174 * Closes the ResultSet (which may be null). 175 * 176 * @param resultSet a ResultSet, may be {@code null} 177 * @deprecated Use {@link #closeQuietly(AutoCloseable)}. 178 */ 179 @Deprecated 180 public static void closeQuietly(final ResultSet resultSet) { 181 closeQuietly((AutoCloseable) resultSet); 182 } 183 184 /** 185 * Closes the Statement (which may be null). 186 * 187 * @param statement a Statement, may be {@code null}. 188 * @deprecated Use {@link #closeQuietly(AutoCloseable)}. 189 */ 190 @Deprecated 191 public static void closeQuietly(final Statement statement) { 192 closeQuietly((AutoCloseable) statement); 193 } 194 195 /** 196 * Gets a copy of SQL codes of fatal connection errors. 197 * <ul> 198 * <li>57P01 (Admin shutdown)</li> 199 * <li>57P02 (Crash shutdown)</li> 200 * <li>57P03 (Cannot connect now)</li> 201 * <li>01002 (SQL92 disconnect error)</li> 202 * <li>JZ0C0 (Sybase disconnect error)</li> 203 * <li>JZ0C1 (Sybase disconnect error)</li> 204 * </ul> 205 * @return A copy SQL codes of fatal connection errors. 206 * @since 2.10.0 207 */ 208 public static Set<String> getDisconnectionSqlCodes() { 209 return new HashSet<>(DISCONNECTION_SQL_CODES); 210 } 211 212 /** 213 * Gets the correct i18n message for the given key. 214 * 215 * @param key The key to look up an i18n message. 216 * @return The i18n message. 217 */ 218 public static String getMessage(final String key) { 219 return getMessage(key, (Object[]) null); 220 } 221 222 /** 223 * Gets the correct i18n message for the given key with placeholders replaced by the supplied arguments. 224 * 225 * @param key A message key. 226 * @param args The message arguments. 227 * @return An i18n message. 228 */ 229 public static String getMessage(final String key, final Object... args) { 230 final String msg = messages.getString(key); 231 if (args == null || args.length == 0) { 232 return msg; 233 } 234 final MessageFormat mf = new MessageFormat(msg); 235 return mf.format(args, new StringBuffer(), null).toString(); 236 } 237 238 /** 239 * Checks if the given SQL state corresponds to a fatal connection error. 240 * 241 * @param sqlState the SQL state to check. 242 * @return true if the SQL state is a fatal connection error, false otherwise. 243 * @since 2.13.0 244 */ 245 static boolean isDisconnectionSqlCode(final String sqlState) { 246 return DISCONNECTION_SQL_CODES.contains(sqlState); 247 } 248 249 static boolean isEmpty(final Collection<?> collection) { 250 return collection == null || collection.isEmpty(); 251 } 252 253 static boolean isSecurityEnabled() { 254 return System.getSecurityManager() != null; 255 } 256 257 /** 258 * Converts the given String to a char[]. 259 * 260 * @param value may be null. 261 * @return a char[] or null. 262 */ 263 public static char[] toCharArray(final String value) { 264 return value != null ? value.toCharArray() : null; 265 } 266 267 /** 268 * Converts the given char[] to a String. 269 * 270 * @param value may be null. 271 * @return a String or null. 272 */ 273 public static String toString(final char[] value) { 274 return value == null ? null : String.valueOf(value); 275 } 276 277 /** 278 * Throws a LifetimeExceededException if the given pooled object's lifetime has exceeded a maximum duration. 279 * 280 * @param p The pooled object to test. 281 * @param maxDuration The maximum lifetime. 282 * @throws LifetimeExceededException Thrown if the given pooled object's lifetime has exceeded a maximum duration. 283 */ 284 public static void validateLifetime(final PooledObject<?> p, final Duration maxDuration) throws LifetimeExceededException { 285 if (maxDuration.compareTo(Duration.ZERO) > 0) { 286 final Duration lifetimeDuration = Duration.between(p.getCreateInstant(), Instant.now()); 287 if (lifetimeDuration.compareTo(maxDuration) > 0) { 288 throw new LifetimeExceededException(getMessage("connectionFactory.lifetimeExceeded", lifetimeDuration, maxDuration)); 289 } 290 } 291 } 292 293 private Utils() { 294 // not instantiable 295 } 296 297}