startTimeHolder = new AtomicReference<>();
212 |
213 | Flux extends Result> queryExecutionFlux = Flux.empty()
214 | .ofType(Result.class)
215 | .doOnSubscribe(s -> {
216 |
217 | Instant startTime = this.clock.instant();
218 | startTimeHolder.set(startTime);
219 |
220 | String threadName = Thread.currentThread().getName();
221 | long threadId = Thread.currentThread().getId();
222 | executionInfo.setThreadName(threadName);
223 | executionInfo.setThreadId(threadId);
224 |
225 | executionInfo.setCurrentMappedResult(null);
226 |
227 | executionInfo.setProxyEventType(ProxyEventType.BEFORE_QUERY);
228 |
229 | listener.onQueryExecution(executionInfo);
230 | })
231 | .concatWith(flux)
232 | .doOnComplete(() -> {
233 | executionInfo.setSuccess(true);
234 | })
235 | .doOnError(throwable -> {
236 | executionInfo.setThrowable(throwable);
237 | executionInfo.setSuccess(false);
238 | })
239 | .doFinally(signalType -> {
240 |
241 | Instant startTime = startTimeHolder.get();
242 | Instant currentTime = this.clock.instant();
243 |
244 | Duration executionDuration = Duration.between(startTime, currentTime);
245 | executionInfo.setExecuteDuration(executionDuration);
246 |
247 | String threadName = Thread.currentThread().getName();
248 | long threadId = Thread.currentThread().getId();
249 | executionInfo.setThreadName(threadName);
250 | executionInfo.setThreadId(threadId);
251 |
252 | executionInfo.setCurrentMappedResult(null);
253 |
254 | executionInfo.setProxyEventType(ProxyEventType.AFTER_QUERY);
255 |
256 | listener.onQueryExecution(executionInfo);
257 | });
258 |
259 | ProxyFactory proxyFactory = this.proxyConfig.getProxyFactory();
260 |
261 | // return a publisher that returns proxy Result
262 | return Flux.from(queryExecutionFlux)
263 | .flatMap(queryResult -> Mono.just(proxyFactory.createResult(queryResult, executionInfo)));
264 |
265 | }
266 |
267 | public void setClock(Clock clock) {
268 | this.clock = clock;
269 | }
270 | }
271 |
--------------------------------------------------------------------------------
/src/main/java/net/ttddyy/dsproxy/r2dbc/proxy/JdkProxyFactory.java:
--------------------------------------------------------------------------------
1 | package net.ttddyy.dsproxy.r2dbc.proxy;
2 |
3 | import io.r2dbc.spi.Batch;
4 | import io.r2dbc.spi.Connection;
5 | import io.r2dbc.spi.ConnectionFactory;
6 | import io.r2dbc.spi.Result;
7 | import io.r2dbc.spi.Statement;
8 | import net.ttddyy.dsproxy.r2dbc.core.ConnectionHolder;
9 | import net.ttddyy.dsproxy.r2dbc.core.ConnectionInfo;
10 | import net.ttddyy.dsproxy.r2dbc.core.QueryExecutionInfo;
11 |
12 | import java.lang.reflect.InvocationHandler;
13 | import java.lang.reflect.Method;
14 | import java.lang.reflect.Proxy;
15 |
16 | /**
17 | * ProxyFactory using JDK dynamic proxy.
18 | *
19 | * @author Tadaya Tsuyukubo
20 | */
21 | public class JdkProxyFactory implements ProxyFactory {
22 |
23 | private ProxyConfig proxyConfig;
24 |
25 | @Override
26 | public void setProxyConfig(ProxyConfig proxyConfig) {
27 | this.proxyConfig = proxyConfig;
28 | }
29 |
30 | @Override
31 | public ConnectionFactory createConnectionFactory(ConnectionFactory connectionFactory) {
32 | return (ConnectionFactory) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
33 | new Class[]{ConnectionFactory.class, ProxyObject.class},
34 | new ConnectionFactoryInvocationHandler(connectionFactory, this.proxyConfig));
35 | }
36 |
37 | @Override
38 | public Connection createConnection(Connection connection, ConnectionInfo connectionInfo) {
39 | return (Connection) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
40 | new Class[]{Connection.class, ProxyObject.class, ConnectionHolder.class},
41 | new ConnectionInvocationHandler(connection, connectionInfo, this.proxyConfig));
42 | }
43 |
44 | @Override
45 | public Batch> createBatch(Batch> batch, ConnectionInfo connectionInfo) {
46 | return (Batch>) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
47 | new Class[]{Batch.class, ProxyObject.class, ConnectionHolder.class},
48 | new BatchInvocationHandler(batch, connectionInfo, this.proxyConfig));
49 | }
50 |
51 | @Override
52 | public Statement> createStatement(Statement> statement, String query, ConnectionInfo connectionInfo) {
53 | return (Statement>) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
54 | new Class[]{Statement.class, ProxyObject.class, ConnectionHolder.class},
55 | new StatementInvocationHandler(statement, query, connectionInfo, this.proxyConfig));
56 | }
57 |
58 | @Override
59 | public Result createResult(Result result, QueryExecutionInfo queryExecutionInfo) {
60 | return (Result) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
61 | new Class[]{Result.class, ProxyObject.class, ConnectionHolder.class},
62 | new ResultInvocationHandler(result, queryExecutionInfo, this.proxyConfig));
63 | }
64 |
65 | public static class ConnectionFactoryInvocationHandler implements InvocationHandler {
66 |
67 | private ReactiveConnectionFactoryCallback delegate;
68 |
69 | public ConnectionFactoryInvocationHandler(ConnectionFactory connectionFactory, ProxyConfig proxyConfig) {
70 | this.delegate = new ReactiveConnectionFactoryCallback(connectionFactory, proxyConfig);
71 | }
72 |
73 | @Override
74 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
75 | return delegate.invoke(proxy, method, args);
76 | }
77 | }
78 |
79 | public static class ConnectionInvocationHandler implements InvocationHandler {
80 |
81 | private ReactiveConnectionCallback delegate;
82 |
83 | public ConnectionInvocationHandler(Connection connection, ConnectionInfo connectionInfo, ProxyConfig proxyConfig) {
84 | this.delegate = new ReactiveConnectionCallback(connection, connectionInfo, proxyConfig);
85 | }
86 |
87 | @Override
88 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
89 | return delegate.invoke(proxy, method, args);
90 | }
91 | }
92 |
93 | public static class BatchInvocationHandler implements InvocationHandler {
94 |
95 | private ReactiveBatchCallback delegate;
96 |
97 | public BatchInvocationHandler(Batch> batch, ConnectionInfo connectionInfo, ProxyConfig proxyConfig) {
98 | this.delegate = new ReactiveBatchCallback(batch, connectionInfo, proxyConfig);
99 | }
100 |
101 | @Override
102 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
103 | return delegate.invoke(proxy, method, args);
104 | }
105 | }
106 |
107 | public static class StatementInvocationHandler implements InvocationHandler {
108 |
109 | private ReactiveStatementCallback delegate;
110 |
111 | public StatementInvocationHandler(Statement> statement, String query, ConnectionInfo connectionInfo, ProxyConfig proxyConfig) {
112 | this.delegate = new ReactiveStatementCallback(statement, query, connectionInfo, proxyConfig);
113 | }
114 |
115 | @Override
116 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
117 | return delegate.invoke(proxy, method, args);
118 | }
119 | }
120 |
121 | public static class ResultInvocationHandler implements InvocationHandler {
122 |
123 | private ReactiveResultCallback delegate;
124 |
125 | public ResultInvocationHandler(Result result, QueryExecutionInfo queryExecutionInfo, ProxyConfig proxyConfig) {
126 | this.delegate = new ReactiveResultCallback(result, queryExecutionInfo, proxyConfig);
127 | }
128 |
129 | @Override
130 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
131 | return delegate.invoke(proxy, method, args);
132 | }
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/src/main/java/net/ttddyy/dsproxy/r2dbc/proxy/ProxyConfig.java:
--------------------------------------------------------------------------------
1 | package net.ttddyy.dsproxy.r2dbc.proxy;
2 |
3 | import net.ttddyy.dsproxy.r2dbc.core.CompositeProxyExecutionListener;
4 | import net.ttddyy.dsproxy.r2dbc.core.ConnectionIdManager;
5 | import net.ttddyy.dsproxy.r2dbc.core.DefaultConnectionIdManager;
6 | import net.ttddyy.dsproxy.r2dbc.core.ProxyExecutionListener;
7 |
8 | /**
9 | *
10 | * @author Tadaya Tsuyukubo
11 | */
12 | public class ProxyConfig {
13 |
14 | private CompositeProxyExecutionListener listeners = new CompositeProxyExecutionListener();
15 |
16 | private ConnectionIdManager connectionIdManager = new DefaultConnectionIdManager();
17 |
18 | private ProxyFactory proxyFactory = new JdkProxyFactory();
19 |
20 | {
21 | this.proxyFactory.setProxyConfig(this);
22 | }
23 |
24 | // public ProxyConfig(ProxyFactory proxyFactory) {
25 | // this.proxyFactory = proxyFactory;
26 | // }
27 |
28 | public ProxyFactory getProxyFactory() {
29 | return proxyFactory;
30 | }
31 |
32 | public void setProxyFactory(ProxyFactory proxyFactory) {
33 | this.proxyFactory = proxyFactory;
34 | this.proxyFactory.setProxyConfig(this);
35 | }
36 |
37 | public CompositeProxyExecutionListener getListeners() {
38 | return this.listeners;
39 | }
40 |
41 | public void addListener(ProxyExecutionListener listener) {
42 | this.listeners.add(listener);
43 | }
44 |
45 | public ConnectionIdManager getConnectionIdManager() {
46 | return connectionIdManager;
47 | }
48 |
49 | public void setConnectionIdManager(ConnectionIdManager connectionIdManager) {
50 | this.connectionIdManager = connectionIdManager;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/net/ttddyy/dsproxy/r2dbc/proxy/ProxyFactory.java:
--------------------------------------------------------------------------------
1 | package net.ttddyy.dsproxy.r2dbc.proxy;
2 |
3 | import io.r2dbc.spi.Batch;
4 | import io.r2dbc.spi.Connection;
5 | import io.r2dbc.spi.ConnectionFactory;
6 | import io.r2dbc.spi.Result;
7 | import io.r2dbc.spi.Statement;
8 | import net.ttddyy.dsproxy.r2dbc.core.ConnectionInfo;
9 | import net.ttddyy.dsproxy.r2dbc.core.QueryExecutionInfo;
10 |
11 | /**
12 | * @author Tadaya Tsuyukubo
13 | */
14 | public interface ProxyFactory {
15 |
16 | void setProxyConfig(ProxyConfig proxyConfig);
17 |
18 | ConnectionFactory createConnectionFactory(ConnectionFactory connectionFactory);
19 |
20 | Connection createConnection(Connection connection, ConnectionInfo connectionInfo);
21 |
22 | Batch> createBatch(Batch> batch, ConnectionInfo connectionInfo);
23 |
24 | Statement> createStatement(Statement> statement, String query, ConnectionInfo connectionInfo);
25 |
26 | Result createResult(Result result, QueryExecutionInfo executionInfo);
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/net/ttddyy/dsproxy/r2dbc/proxy/ProxyObject.java:
--------------------------------------------------------------------------------
1 | package net.ttddyy.dsproxy.r2dbc.proxy;
2 |
3 | import net.ttddyy.dsproxy.r2dbc.proxy.ProxyFactory;
4 | import net.ttddyy.dsproxy.r2dbc.proxy.ReactiveBatchCallback;
5 | import net.ttddyy.dsproxy.r2dbc.proxy.ReactiveConnectionCallback;
6 | import net.ttddyy.dsproxy.r2dbc.proxy.ReactiveConnectionFactoryCallback;
7 | import net.ttddyy.dsproxy.r2dbc.proxy.ReactiveStatementCallback;
8 |
9 | /**
10 | * Provide a method to unwrap the original object from proxy object.
11 | *
12 | * Proxy object created by {@link ProxyFactory} implements this interface.
13 | *
14 | * @author Tadaya Tsuyukubo
15 | * @see ProxyFactory
16 | * @see ReactiveConnectionFactoryCallback
17 | * @see ReactiveConnectionCallback
18 | * @see ReactiveBatchCallback
19 | * @see ReactiveStatementCallback
20 | */
21 | public interface ProxyObject {
22 |
23 | /**
24 | * Method to return the source object (ConnectionFactory, Connection, Batch, Statement).
25 | *
26 | * @return source object
27 | */
28 | Object getTarget();
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/net/ttddyy/dsproxy/r2dbc/proxy/ProxyUtils.java:
--------------------------------------------------------------------------------
1 | package net.ttddyy.dsproxy.r2dbc.proxy;
2 |
3 | import io.r2dbc.spi.Batch;
4 | import io.r2dbc.spi.Connection;
5 | import io.r2dbc.spi.Result;
6 | import io.r2dbc.spi.Statement;
7 | import net.ttddyy.dsproxy.r2dbc.core.ConnectionHolder;
8 |
9 | /**
10 | * @author Tadaya Tsuyukubo
11 | */
12 | public class ProxyUtils {
13 |
14 | private ProxyUtils() {
15 | }
16 |
17 | public static Connection getOriginalConnection(Connection connection) {
18 | if (connection instanceof ProxyObject) {
19 | return (Connection) ((ProxyObject) connection).getTarget();
20 | }
21 | return connection;
22 | }
23 |
24 | public static Connection getOriginalConnection(Batch> batch) {
25 | if (batch instanceof ConnectionHolder) {
26 | return ((ConnectionHolder) batch).getOriginalConnection();
27 | }
28 | return null;
29 | }
30 |
31 | public static Connection getOriginalConnection(Statement> statement) {
32 | if (statement instanceof ConnectionHolder) {
33 | return ((ConnectionHolder) statement).getOriginalConnection();
34 | }
35 | return null;
36 | }
37 |
38 | public static Connection getOriginalConnection(Result result) {
39 | if (result instanceof ConnectionHolder) {
40 | return ((ConnectionHolder) result).getOriginalConnection();
41 | }
42 | return null;
43 | }
44 |
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/net/ttddyy/dsproxy/r2dbc/proxy/ReactiveBatchCallback.java:
--------------------------------------------------------------------------------
1 | package net.ttddyy.dsproxy.r2dbc.proxy;
2 |
3 | import io.r2dbc.spi.Batch;
4 | import io.r2dbc.spi.Result;
5 | import net.ttddyy.dsproxy.r2dbc.core.ConnectionInfo;
6 | import net.ttddyy.dsproxy.r2dbc.core.ExecutionType;
7 | import net.ttddyy.dsproxy.r2dbc.core.QueryExecutionInfo;
8 | import net.ttddyy.dsproxy.r2dbc.core.QueryInfo;
9 | import org.reactivestreams.Publisher;
10 |
11 | import java.lang.reflect.Method;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | import static java.util.stream.Collectors.toList;
16 |
17 | /**
18 | * Proxy callback for {@link Batch}.
19 | *
20 | * @author Tadaya Tsuyukubo
21 | */
22 | public class ReactiveBatchCallback extends CallbackSupport {
23 |
24 | private Batch> batch;
25 |
26 | private ConnectionInfo connectionInfo;
27 | private List queries = new ArrayList<>();
28 |
29 | public ReactiveBatchCallback(Batch> batch, ConnectionInfo connectionInfo, ProxyConfig proxyConfig) {
30 | super(proxyConfig);
31 | this.batch = batch;
32 | this.connectionInfo = connectionInfo;
33 | }
34 |
35 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
36 |
37 | String methodName = method.getName();
38 |
39 | if ("getTarget".equals(methodName)) {
40 | return this.batch;
41 | } else if ("getOriginalConnection".equals(methodName)) {
42 | return this.connectionInfo.getOriginalConnection();
43 | }
44 |
45 | Object result = proceedExecution(method, this.batch, args, this.proxyConfig.getListeners(), this.connectionInfo, null, null);
46 |
47 | if ("add".equals(methodName)) {
48 | this.queries.add((String) args[0]);
49 | } else if ("execute".equals(methodName)) {
50 |
51 | List queryInfoList = this.queries.stream()
52 | .map(QueryInfo::new)
53 | .collect(toList());
54 |
55 | QueryExecutionInfo execInfo = new QueryExecutionInfo();
56 | execInfo.setType(ExecutionType.BATCH);
57 | execInfo.setQueries(queryInfoList);
58 | execInfo.setBatchSize(this.queries.size());
59 | execInfo.setMethod(method);
60 | execInfo.setMethodArgs(args);
61 | execInfo.setConnectionInfo(this.connectionInfo);
62 |
63 | // API defines "execute()" returns a publisher
64 | Publisher extends Result> publisher = (Publisher extends Result>) result;
65 |
66 | return interceptQueryExecution(publisher, execInfo);
67 | }
68 |
69 | return result;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/net/ttddyy/dsproxy/r2dbc/proxy/ReactiveConnectionCallback.java:
--------------------------------------------------------------------------------
1 | package net.ttddyy.dsproxy.r2dbc.proxy;
2 |
3 | import io.r2dbc.spi.Batch;
4 | import io.r2dbc.spi.Connection;
5 | import io.r2dbc.spi.Statement;
6 | import net.ttddyy.dsproxy.r2dbc.core.ConnectionInfo;
7 | import net.ttddyy.dsproxy.r2dbc.core.MethodExecutionInfo;
8 |
9 | import java.lang.reflect.Method;
10 | import java.util.function.Consumer;
11 |
12 | /**
13 | * Proxy callback for {@link Connection}.
14 | *
15 | * @author Tadaya Tsuyukubo
16 | */
17 | public class ReactiveConnectionCallback extends CallbackSupport {
18 |
19 | private Connection connection;
20 | private ConnectionInfo connectionInfo;
21 |
22 | public ReactiveConnectionCallback(Connection connection, ConnectionInfo connectionInfo, ProxyConfig proxyConfig) {
23 | super(proxyConfig);
24 | this.connection = connection;
25 | this.connectionInfo = connectionInfo;
26 | }
27 |
28 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
29 |
30 | String methodName = method.getName();
31 |
32 | if ("getTarget".equals(methodName)) {
33 | return this.connection;
34 | } else if ("getOriginalConnection".equals(methodName)) {
35 | return this.connection;
36 | }
37 |
38 | Consumer onComplete = null;
39 |
40 | // since these methods return Publisher pass the callback for doOnComplete().
41 | if ("beginTransaction".equals(methodName)) {
42 | onComplete = executionInfo -> {
43 | executionInfo.getConnectionInfo().incrementTransactionCount();
44 | };
45 | } else if ("commitTransaction".equals(methodName)) {
46 | onComplete = executionInfo -> {
47 | executionInfo.getConnectionInfo().incrementCommitCount();
48 | };
49 | } else if ("rollbackTransaction".equals(methodName)) {
50 | onComplete = executionInfo -> {
51 | executionInfo.getConnectionInfo().incrementRollbackCount();
52 | };
53 | } else if ("close".equals(methodName)) {
54 | onComplete = executionInfo -> {
55 | executionInfo.getConnectionInfo().setClosed(true);
56 | };
57 | }
58 | // TODO: createSavepoint, releaseSavepoint, rollbackTransactionToSavepoint
59 |
60 | Object result = proceedExecution(method, this.connection, args, this.proxyConfig.getListeners(), this.connectionInfo, null, onComplete);
61 |
62 | if ("createBatch".equals(methodName)) {
63 | return this.proxyConfig.getProxyFactory().createBatch((Batch) result, this.connectionInfo);
64 | } else if ("createStatement".equals(methodName)) {
65 | String query = (String) args[0];
66 | return this.proxyConfig.getProxyFactory().createStatement((Statement) result, query, this.connectionInfo);
67 | }
68 |
69 | return result;
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/net/ttddyy/dsproxy/r2dbc/proxy/ReactiveConnectionFactoryCallback.java:
--------------------------------------------------------------------------------
1 | package net.ttddyy.dsproxy.r2dbc.proxy;
2 |
3 | import io.r2dbc.spi.Connection;
4 | import io.r2dbc.spi.ConnectionFactory;
5 | import net.ttddyy.dsproxy.r2dbc.core.ConnectionInfo;
6 | import net.ttddyy.dsproxy.r2dbc.core.MethodExecutionInfo;
7 |
8 | import java.lang.reflect.Method;
9 | import java.util.function.BiFunction;
10 |
11 | /**
12 | * Proxy callback for {@link ConnectionFactory}.
13 | *
14 | * @author Tadaya Tsuyukubo
15 | */
16 | public class ReactiveConnectionFactoryCallback extends CallbackSupport {
17 |
18 | private ConnectionFactory connectionFactory;
19 |
20 | public ReactiveConnectionFactoryCallback(ConnectionFactory connectionFactory, ProxyConfig proxyConfig) {
21 | super(proxyConfig);
22 | this.connectionFactory = connectionFactory;
23 | }
24 |
25 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
26 |
27 | String methodName = method.getName();
28 |
29 | if ("getTarget".equals(methodName)) {
30 | return this.connectionFactory;
31 | }
32 |
33 | BiFunction