org.apache.openejb.resource.jdbc.ResettableDataSourceHandler Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.openejb.resource.jdbc;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;
import javax.sql.CommonDataSource;
import java.io.Flushable;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Arrays.asList;
public class ResettableDataSourceHandler implements DelegatableHandler {
private static final Logger LOGGER = Logger.getInstance(LogCategory.OPENEJB, ResettableDataSourceHandler.class.getName());
private final AtomicReference delegate = new AtomicReference<>();
private final RetryStrategy strategy; // TODO: add pause/exp backoff strategy
private Set retryMethods = new HashSet<>();
public ResettableDataSourceHandler(final CommonDataSource ds, final String value, final String methods) {
this.delegate.set(ds);
if (!"*".equals(methods)) {
this.retryMethods.addAll(asList(methods == null ? new String[]{"getConnection", "getXAConnection"} : methods.split(" *, *")));
}
final Runnable recreate = new Runnable() {
@Override
public void run() {
try {
Flushable.class.cast(delegate.get()).flush();
} catch (final IOException ioe) {
LOGGER.error("Can't flush connection pool: " + ioe.getMessage());
}
}
};
RetryStrategy tmp;
if (value.equals("true")) {
tmp = new CountRetryStrategy(recreate, 1);
} else if (value.startsWith("retry(") && value.endsWith(")")) {
tmp = new CountRetryStrategy(recreate, Integer.parseInt(value.substring("retry(".length(), value.length() - 1)));
} else {
try {
tmp = new CountRetryStrategy(recreate, Integer.parseInt(value.trim()));
} catch (final NumberFormatException nfe) {
try {
tmp = RetryStrategy.class.cast(Thread.currentThread().getContextClassLoader().loadClass(value)
.getConstructor(Runnable.class, String.class).newInstance(recreate, value));
} catch (final InstantiationException | IllegalAccessException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException e) {
throw new IllegalArgumentException("Unknown retry strategy: " + value, e);
}
}
}
strategy = tmp;
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
if (Object.class == method.getDeclaringClass() && "toString".equals(method.getName())) {
return "Resettable[" + getDelegate().toString() + "]";
}
Result retry = null;
do {
try {
return method.invoke(getDelegate(), args);
} catch (final InvocationTargetException ite) {
final Throwable cause = ite.getCause();
if (SQLException.class.isInstance(cause) && isRetryMethod(method)) {
retry = strategy.shouldRetry(cause, retry);
if (!retry.status) {
throw cause;
} else {
continue;
}
}
throw cause;
}
} while (true);
}
private boolean isRetryMethod(final Method method) {
return retryMethods.isEmpty() /* wildcard */ || retryMethods.contains(method.getName());
}
@Override
public CommonDataSource getDelegate() {
return delegate.get();
}
public void updateDelegate(final CommonDataSource ds) {
delegate.set(ds);
}
public interface RetryStrategy {
Result shouldRetry(Throwable cause, Result previous);
}
public static class Result {
private final boolean status;
public Result(final boolean status) {
this.status = status;
}
}
private static class CountRetryStrategy implements RetryStrategy {
private final Runnable task;
private final int max;
public CountRetryStrategy(final Runnable recreate, final int max) {
this.task = recreate;
this.max = max;
}
@Override
public Result shouldRetry(final Throwable cause, final Result previous) {
LOGGER.error("SQLException, resetting the connection pool.", cause);
final Integer count = previous == null ? 1 : CountResult.class.cast(previous).count + 1;
task.run();
return new CountResult(count <= max, count);
}
}
private static class CountResult extends Result {
private int count;
public CountResult(final boolean status, final int count) {
super(status);
this.count = count;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy