View Javadoc
1   /*
2    * Copyright (c) 2001-2017, Zoltan Farkas All Rights Reserved.
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Lesser General Public
6    * License as published by the Free Software Foundation; either
7    * version 2.1 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this program; if not, write to the Free Software
16   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17   *
18   * Additionally licensed with:
19   *
20   * Licensed under the Apache License, Version 2.0 (the "License");
21   * you may not use this file except in compliance with the License.
22   * You may obtain a copy of the License at
23   *
24   *      http://www.apache.org/licenses/LICENSE-2.0
25   *
26   * Unless required by applicable law or agreed to in writing, software
27   * distributed under the License is distributed on an "AS IS" BASIS,
28   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29   * See the License for the specific language governing permissions and
30   * limitations under the License.
31   */
32  package org.spf4j.jdbc;
33  
34  import com.google.common.annotations.Beta;
35  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
36  import java.sql.Connection;
37  import java.sql.SQLException;
38  import java.sql.SQLTimeoutException;
39  import java.util.concurrent.Callable;
40  import java.util.concurrent.TimeUnit;
41  import java.util.concurrent.TimeoutException;
42  import javax.annotation.Nonnegative;
43  import javax.annotation.Signed;
44  import javax.sql.DataSource;
45  import org.spf4j.base.ExecutionContext;
46  import org.spf4j.base.ExecutionContexts;
47  import org.spf4j.base.HandlerNano;
48  import org.spf4j.base.JavaUtils;
49  import org.spf4j.base.TimeSource;
50  import org.spf4j.failsafe.RetryPolicy;
51  
52  /**
53   * A very simple JdbTemplate.
54   *
55   * @author zoly
56   */
57  @Beta
58  public final class JdbcTemplate {
59  
60    /**
61     * certain JDBC drivers multiply timeout to transform in milis or nanos without validating overflow.
62     * This value needs to be low enough to account for this case.
63     */
64    private static final int MAX_JDBC_TIMEOUTSECONDS =
65            Integer.getInteger("spf4j.jdbc.maxdbcTimeoutSeconds", 3600 * 24);
66  
67  
68    private final DataSource dataSource;
69  
70    private final RetryPolicy<Object, Callable<? extends Object>> retryPolicy;
71  
72    public JdbcTemplate(final DataSource dataSource) {
73      this(dataSource, RetryPolicy.newBuilder()
74              .withDefaultThrowableRetryPredicate()
75              .build());
76    }
77  
78    @SuppressFBWarnings("EI_EXPOSE_REP2") // DataSource is mutable...
79    public JdbcTemplate(final DataSource dataSource, final RetryPolicy<Object, Callable<? extends Object>> retryPolicy) {
80      this.dataSource = dataSource;
81      this.retryPolicy = retryPolicy;
82    }
83  
84    public static void checkJdbcObjectName(final CharSequence name) {
85      if (!JavaUtils.isJavaIdentifier(name) || name.length() > 30) {
86        throw new IllegalArgumentException("Invalid database Object identifier " + name);
87      }
88    }
89  
90    public <R> R transactOnConnection(final HandlerNano<Connection, R, SQLException> handler,
91            final long timeout, final TimeUnit tu) throws SQLException, InterruptedException {
92      return transactOnConnection(handler, ExecutionContexts.computeDeadline(timeout, tu));
93    }
94  
95  
96    @SuppressFBWarnings("BED_BOGUS_EXCEPTION_DECLARATION")
97    public <R> R transactOnConnection(final HandlerNano<Connection, R, SQLException> handler,
98             final long deadlineNanos)
99            throws SQLException, InterruptedException {
100     try (ExecutionContext ctx = ExecutionContexts.start(handler.toString(), deadlineNanos)) {
101       return (R) retryPolicy.call(new Callable() {
102         @Override
103         public R call() throws SQLException {
104           try (Connection conn = dataSource.getConnection()) {
105             boolean autocomit = conn.getAutoCommit();
106             if (autocomit) {
107               conn.setAutoCommit(false);
108             }
109             try {
110               R result = handler.handle(conn, ctx.getDeadlineNanos());
111               conn.commit();
112               return result;
113             } catch (SQLException | RuntimeException ex) {
114               conn.rollback();
115               throw ex;
116             } finally {
117               if (autocomit) {
118                 conn.setAutoCommit(true);
119               }
120             }
121           }
122         }
123       }, SQLException.class, ctx.getDeadlineNanos());
124     } catch (TimeoutException ex) {
125       throw new SQLTimeoutException(ex);
126     }
127   }
128 
129   @SuppressFBWarnings("BED_BOGUS_EXCEPTION_DECLARATION")
130   public <R> R transactOnConnectionNonInterrupt(final HandlerNano<Connection, R, SQLException> handler,
131           final long timeout, final TimeUnit tu)
132           throws SQLException {
133     try {
134       return transactOnConnection(handler, timeout, tu);
135     } catch (InterruptedException ex) {
136       Thread.currentThread().interrupt();
137       throw new SQLException(ex);
138     }
139   }
140 
141 
142   /**
143    * @param deadlineNanos the deadline relative to the same as System.nanoTime()
144    * @return
145    */
146   @Nonnegative
147   public static int getTimeoutToDeadlineSeconds(final long deadlineNanos) throws SQLTimeoutException {
148     long toSeconds = TimeUnit.NANOSECONDS.toSeconds(deadlineNanos - TimeSource.nanoTime());
149     if (toSeconds < 0L) {
150       throw new SQLTimeoutException("deadline exceeded by " + (-toSeconds) + " seconds");
151     }
152     if (toSeconds == 0) {
153       return 1;
154     }
155     if (toSeconds > MAX_JDBC_TIMEOUTSECONDS) {
156       return MAX_JDBC_TIMEOUTSECONDS;
157     } else {
158      return (int) toSeconds;
159     }
160   }
161 
162   @Signed
163   public static int getTimeoutToDeadlineSecondsNoEx(final long deadlineNanos) {
164     long toSeconds = TimeUnit.NANOSECONDS.toSeconds(deadlineNanos - TimeSource.nanoTime());
165     if (toSeconds == 0) {
166       return 1;
167     }
168     if (toSeconds > MAX_JDBC_TIMEOUTSECONDS) {
169       return MAX_JDBC_TIMEOUTSECONDS;
170     } else {
171      return (int) toSeconds;
172     }
173   }
174 
175   @Override
176   public String toString() {
177     return "JdbcTemplate{" + "dataSource=" + dataSource + '}';
178   }
179 
180 }