1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.dbcp2;
18
19 import java.sql.Connection;
20 import java.sql.ResultSet;
21 import java.sql.Statement;
22 import java.text.MessageFormat;
23 import java.time.Duration;
24 import java.time.Instant;
25 import java.util.Collection;
26 import java.util.HashSet;
27 import java.util.Properties;
28 import java.util.ResourceBundle;
29 import java.util.Set;
30 import java.util.function.Consumer;
31
32 import org.apache.commons.pool2.PooledObject;
33
34 /**
35 * Utility methods.
36 *
37 * @since 2.0
38 */
39 public final class Utils {
40
41 private static final ResourceBundle messages = ResourceBundle
42 .getBundle(Utils.class.getPackage().getName() + ".LocalStrings");
43
44 /**
45 * Whether the security manager is enabled.
46 *
47 * @deprecated No replacement.
48 */
49 @Deprecated
50 public static final boolean IS_SECURITY_ENABLED = isSecurityEnabled();
51
52 /**
53 * Any SQL State starting with this value is considered a fatal disconnect.
54 */
55 public static final String DISCONNECTION_SQL_CODE_PREFIX = "08";
56
57 /**
58 * SQL codes of fatal connection errors.
59 * <ul>
60 * <li>57P01 (Admin shutdown)</li>
61 * <li>57P02 (Crash shutdown)</li>
62 * <li>57P03 (Cannot connect now)</li>
63 * <li>01002 (SQL92 disconnect error)</li>
64 * <li>JZ0C0 (Sybase disconnect error)</li>
65 * <li>JZ0C1 (Sybase disconnect error)</li>
66 * </ul>
67 * @deprecated Use {@link #getDisconnectionSqlCodes()}.
68 */
69 @Deprecated
70 public static final Set<String> DISCONNECTION_SQL_CODES;
71
72 static final ResultSet[] EMPTY_RESULT_SET_ARRAY = {};
73
74 static final String[] EMPTY_STRING_ARRAY = {};
75 static {
76 DISCONNECTION_SQL_CODES = new HashSet<>();
77 DISCONNECTION_SQL_CODES.add("57P01"); // Admin shutdown
78 DISCONNECTION_SQL_CODES.add("57P02"); // Crash shutdown
79 DISCONNECTION_SQL_CODES.add("57P03"); // Cannot connect now
80 DISCONNECTION_SQL_CODES.add("01002"); // SQL92 disconnect error
81 DISCONNECTION_SQL_CODES.add("JZ0C0"); // Sybase disconnect error
82 DISCONNECTION_SQL_CODES.add("JZ0C1"); // Sybase disconnect error
83 }
84
85 /**
86 * Checks for conflicts between two collections.
87 * <p>
88 * If any overlap is found between the two provided collections, an {@link IllegalArgumentException} is thrown.
89 * </p>
90 *
91 * @param codes1 The first collection of SQL state codes.
92 * @param codes2 The second collection of SQL state codes.
93 * @throws IllegalArgumentException if any codes overlap between the two collections.
94 * @since 2.13.0
95 */
96 static void checkSqlCodes(final Collection<String> codes1, final Collection<String> codes2) {
97 if (codes1 != null && codes2 != null) {
98 final Set<String> test = new HashSet<>(codes1);
99 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 }