InstanceKeyDataSourceFactory.java

  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.datasources;

  18. import java.io.ByteArrayInputStream;
  19. import java.io.IOException;
  20. import java.io.ObjectInputStream;
  21. import java.time.Duration;
  22. import java.util.ArrayList;
  23. import java.util.Hashtable;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Properties;
  27. import java.util.concurrent.ConcurrentHashMap;

  28. import javax.naming.Context;
  29. import javax.naming.Name;
  30. import javax.naming.RefAddr;
  31. import javax.naming.Reference;
  32. import javax.naming.spi.ObjectFactory;

  33. import org.apache.commons.dbcp2.ListException;
  34. import org.apache.commons.dbcp2.Utils;

  35. /**
  36.  * A JNDI ObjectFactory which creates {@code SharedPoolDataSource}s or {@code PerUserPoolDataSource}s
  37.  *
  38.  * @since 2.0
  39.  */
  40. abstract class InstanceKeyDataSourceFactory implements ObjectFactory {

  41.     private static final Map<String, InstanceKeyDataSource> INSTANCE_MAP = new ConcurrentHashMap<>();

  42.     /**
  43.      * Closes all pools associated with this class.
  44.      *
  45.      * @throws ListException
  46.      *             a {@link ListException} containing all exceptions thrown by {@link InstanceKeyDataSource#close()}
  47.      * @see InstanceKeyDataSource#close()
  48.      * @since 2.4.0 throws a {@link ListException} instead of, in 2.3.0 and before, the first exception thrown by
  49.      *        {@link InstanceKeyDataSource#close()}.
  50.      */
  51.     public static void closeAll() throws ListException {
  52.         // Get iterator to loop over all instances of this data source.
  53.         final List<Throwable> exceptionList = new ArrayList<>(INSTANCE_MAP.size());
  54.         INSTANCE_MAP.entrySet().forEach(entry -> {
  55.             // Bullet-proof to avoid anything else but problems from InstanceKeyDataSource#close().
  56.             if (entry != null) {
  57.                 @SuppressWarnings("resource")
  58.                 final InstanceKeyDataSource value = entry.getValue();
  59.                 Utils.close(value, exceptionList::add);
  60.             }
  61.         });
  62.         INSTANCE_MAP.clear();
  63.         if (!exceptionList.isEmpty()) {
  64.             throw new ListException("Could not close all InstanceKeyDataSource instances.", exceptionList);
  65.         }
  66.     }

  67.     /**
  68.      * Deserializes the provided byte array to create an object.
  69.      *
  70.      * @param data
  71.      *            Data to deserialize to create the configuration parameter.
  72.      *
  73.      * @return The Object created by deserializing the data.
  74.      *
  75.      * @throws ClassNotFoundException
  76.      *            If a class cannot be found during the deserialization of a configuration parameter.
  77.      * @throws IOException
  78.      *            If an I/O error occurs during the deserialization of a configuration parameter.
  79.      */
  80.     protected static final Object deserialize(final byte[] data) throws IOException, ClassNotFoundException {
  81.         ObjectInputStream in = null;
  82.         try {
  83.             in = new ObjectInputStream(new ByteArrayInputStream(data));
  84.             return in.readObject();
  85.         } finally {
  86.             Utils.closeQuietly(in);
  87.         }
  88.     }

  89.     static synchronized String registerNewInstance(final InstanceKeyDataSource ds) {
  90.         int max = 0;
  91.         for (final String s : INSTANCE_MAP.keySet()) {
  92.             if (s != null) {
  93.                 try {
  94.                     max = Math.max(max, Integer.parseInt(s));
  95.                 } catch (final NumberFormatException ignored) {
  96.                     // no sweat, ignore those keys
  97.                 }
  98.             }
  99.         }
  100.         final String instanceKey = String.valueOf(max + 1);
  101.         // Put a placeholder here for now, so other instances will not
  102.         // take our key. We will replace with a pool when ready.
  103.         INSTANCE_MAP.put(instanceKey, ds);
  104.         return instanceKey;
  105.     }

  106.     static void removeInstance(final String key) {
  107.         if (key != null) {
  108.             INSTANCE_MAP.remove(key);
  109.         }
  110.     }

  111.     private Boolean booleanValueOf(RefAddr refAddr) {
  112.         return Boolean.valueOf(toString(refAddr));
  113.     }

  114.     /**
  115.      * Creates an instance of the subclass and sets any properties contained in the Reference.
  116.      *
  117.      * @param ref
  118.      *            The properties to be set on the created DataSource
  119.      *
  120.      * @return A configured DataSource of the appropriate type.
  121.      *
  122.      * @throws ClassNotFoundException
  123.      *            If a class cannot be found during the deserialization of a configuration parameter.
  124.      * @throws IOException
  125.      *            If an I/O error occurs during the deserialization of a configuration parameter.
  126.      */
  127.     protected abstract InstanceKeyDataSource getNewInstance(Reference ref) throws IOException, ClassNotFoundException;

  128.     /**
  129.      * Implements ObjectFactory to create an instance of SharedPoolDataSource or PerUserPoolDataSource
  130.      */
  131.     @Override
  132.     public Object getObjectInstance(final Object refObj, final Name name, final Context context,
  133.             final Hashtable<?, ?> env) throws IOException, ClassNotFoundException {
  134.         // The spec says to return null if we can't create an instance
  135.         // of the reference
  136.         Object obj = null;
  137.         if (refObj instanceof Reference) {
  138.             final Reference ref = (Reference) refObj;
  139.             if (isCorrectClass(ref.getClassName())) {
  140.                 final RefAddr refAddr = ref.get("instanceKey");
  141.                 if (hasContent(refAddr)) {
  142.                     // object was bound to JNDI via Referenceable API.
  143.                     obj = INSTANCE_MAP.get(refAddr.getContent());
  144.                 } else {
  145.                     // Tomcat JNDI creates a Reference out of server.xml
  146.                     // <ResourceParam> configuration and passes it to an
  147.                     // instance of the factory given in server.xml.
  148.                     String key = null;
  149.                     if (name != null) {
  150.                         key = name.toString();
  151.                         obj = INSTANCE_MAP.get(key);
  152.                     }
  153.                     if (obj == null) {
  154.                         final InstanceKeyDataSource ds = getNewInstance(ref);
  155.                         setCommonProperties(ref, ds);
  156.                         obj = ds;
  157.                         if (key != null) {
  158.                             INSTANCE_MAP.put(key, ds);
  159.                         }
  160.                     }
  161.                 }
  162.             }
  163.         }
  164.         return obj;
  165.     }

  166.     private boolean hasContent(final RefAddr refAddr) {
  167.         return refAddr != null && refAddr.getContent() != null;
  168.     }

  169.     /**
  170.      * Tests if className is the value returned from getClass().getName().toString().
  171.      *
  172.      * @param className
  173.      *            The class name to test.
  174.      *
  175.      * @return true if and only if className is the value returned from getClass().getName().toString()
  176.      */
  177.     protected abstract boolean isCorrectClass(String className);

  178.     boolean parseBoolean(final RefAddr refAddr) {
  179.         return Boolean.parseBoolean(toString(refAddr));
  180.     }

  181.     int parseInt(final RefAddr refAddr) {
  182.         return Integer.parseInt(toString(refAddr));
  183.     }

  184.     long parseLong(final RefAddr refAddr) {
  185.         return Long.parseLong(toString(refAddr));
  186.     }

  187.     private void setCommonProperties(final Reference ref, final InstanceKeyDataSource ikds)
  188.             throws IOException, ClassNotFoundException {

  189.         RefAddr refAddr = ref.get("dataSourceName");
  190.         if (hasContent(refAddr)) {
  191.             ikds.setDataSourceName(toString(refAddr));
  192.         }

  193.         refAddr = ref.get("description");
  194.         if (hasContent(refAddr)) {
  195.             ikds.setDescription(toString(refAddr));
  196.         }

  197.         refAddr = ref.get("jndiEnvironment");
  198.         if (hasContent(refAddr)) {
  199.             final byte[] serialized = (byte[]) refAddr.getContent();
  200.             ikds.setJndiEnvironment((Properties) deserialize(serialized));
  201.         }

  202.         refAddr = ref.get("loginTimeout");
  203.         if (hasContent(refAddr)) {
  204.             ikds.setLoginTimeout(toDurationFromSeconds(refAddr));
  205.         }

  206.         // Pool properties
  207.         refAddr = ref.get("blockWhenExhausted");
  208.         if (hasContent(refAddr)) {
  209.             ikds.setDefaultBlockWhenExhausted(parseBoolean(refAddr));
  210.         }

  211.         refAddr = ref.get("evictionPolicyClassName");
  212.         if (hasContent(refAddr)) {
  213.             ikds.setDefaultEvictionPolicyClassName(toString(refAddr));
  214.         }

  215.         // Pool properties
  216.         refAddr = ref.get("lifo");
  217.         if (hasContent(refAddr)) {
  218.             ikds.setDefaultLifo(parseBoolean(refAddr));
  219.         }

  220.         refAddr = ref.get("maxIdlePerKey");
  221.         if (hasContent(refAddr)) {
  222.             ikds.setDefaultMaxIdle(parseInt(refAddr));
  223.         }

  224.         refAddr = ref.get("maxTotalPerKey");
  225.         if (hasContent(refAddr)) {
  226.             ikds.setDefaultMaxTotal(parseInt(refAddr));
  227.         }

  228.         refAddr = ref.get("maxWaitMillis");
  229.         if (hasContent(refAddr)) {
  230.             ikds.setDefaultMaxWait(toDurationFromMillis(refAddr));
  231.         }

  232.         refAddr = ref.get("minEvictableIdleTimeMillis");
  233.         if (hasContent(refAddr)) {
  234.             ikds.setDefaultMinEvictableIdle(toDurationFromMillis(refAddr));
  235.         }

  236.         refAddr = ref.get("minIdlePerKey");
  237.         if (hasContent(refAddr)) {
  238.             ikds.setDefaultMinIdle(parseInt(refAddr));
  239.         }

  240.         refAddr = ref.get("numTestsPerEvictionRun");
  241.         if (hasContent(refAddr)) {
  242.             ikds.setDefaultNumTestsPerEvictionRun(parseInt(refAddr));
  243.         }

  244.         refAddr = ref.get("softMinEvictableIdleTimeMillis");
  245.         if (hasContent(refAddr)) {
  246.             ikds.setDefaultSoftMinEvictableIdle(toDurationFromMillis(refAddr));
  247.         }

  248.         refAddr = ref.get("testOnCreate");
  249.         if (hasContent(refAddr)) {
  250.             ikds.setDefaultTestOnCreate(parseBoolean(refAddr));
  251.         }

  252.         refAddr = ref.get("testOnBorrow");
  253.         if (hasContent(refAddr)) {
  254.             ikds.setDefaultTestOnBorrow(parseBoolean(refAddr));
  255.         }

  256.         refAddr = ref.get("testOnReturn");
  257.         if (hasContent(refAddr)) {
  258.             ikds.setDefaultTestOnReturn(parseBoolean(refAddr));
  259.         }

  260.         refAddr = ref.get("testWhileIdle");
  261.         if (hasContent(refAddr)) {
  262.             ikds.setDefaultTestWhileIdle(parseBoolean(refAddr));
  263.         }

  264.         refAddr = ref.get("timeBetweenEvictionRunsMillis");
  265.         if (hasContent(refAddr)) {
  266.             ikds.setDefaultDurationBetweenEvictionRuns(toDurationFromMillis(refAddr));
  267.         }

  268.         // Connection factory properties

  269.         refAddr = ref.get("validationQuery");
  270.         if (hasContent(refAddr)) {
  271.             ikds.setValidationQuery(toString(refAddr));
  272.         }

  273.         refAddr = ref.get("validationQueryTimeout");
  274.         if (hasContent(refAddr)) {
  275.             ikds.setValidationQueryTimeout(toDurationFromSeconds(refAddr));
  276.         }

  277.         refAddr = ref.get("rollbackAfterValidation");
  278.         if (hasContent(refAddr)) {
  279.             ikds.setRollbackAfterValidation(parseBoolean(refAddr));
  280.         }

  281.         refAddr = ref.get("maxConnLifetimeMillis");
  282.         if (hasContent(refAddr)) {
  283.             ikds.setMaxConnLifetime(toDurationFromMillis(refAddr));
  284.         }

  285.         // Connection properties

  286.         refAddr = ref.get("defaultAutoCommit");
  287.         if (hasContent(refAddr)) {
  288.             ikds.setDefaultAutoCommit(booleanValueOf(refAddr));
  289.         }

  290.         refAddr = ref.get("defaultTransactionIsolation");
  291.         if (hasContent(refAddr)) {
  292.             ikds.setDefaultTransactionIsolation(parseInt(refAddr));
  293.         }

  294.         refAddr = ref.get("defaultReadOnly");
  295.         if (hasContent(refAddr)) {
  296.             ikds.setDefaultReadOnly(booleanValueOf(refAddr));
  297.         }
  298.     }

  299.     private Duration toDurationFromMillis(RefAddr refAddr) {
  300.         return Duration.ofMillis(parseLong(refAddr));
  301.     }

  302.     private Duration toDurationFromSeconds(RefAddr refAddr) {
  303.         return Duration.ofSeconds(parseInt(refAddr));
  304.     }

  305.     String toString(final RefAddr refAddr) {
  306.         return refAddr.getContent().toString();
  307.     }
  308. }