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}