    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 *   https://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}