View Javadoc
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 }