Skip to content

Commit 35319b2

Browse files
authored
Merge pull request oracle-samples#3 from oracle/master
update to latest
2 parents da4e302 + e291c33 commit 35319b2

31 files changed

+2892
-4
lines changed

java/AoJ/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# AoJ: ADBA over JDBC
2+
3+
ADBA is Asynchronous Database Access, a non-blocking database access api that Oracle is proposing as a Java standard. ADBA was announced at [JavaOne 2016](https://static.rainfocus.com/oracle/oow16/sess/1461693351182001EmRq/ppt/CONF1578%2020160916.pdf) and presented again at [JavaOne 2017](http://www.oracle.com/technetwork/database/application-development/jdbc/con1491-3961036.pdf). The ADBA source is available for download from the [OpenJDK sandbox](
4+
http://hg.openjdk.java.net/jdk/sandbox/file/9d3b0eb749a9/src/jdk.incubator.adba) as part of the OpenJDK project. You can get involved in the ADBA specification effort by following the [JDBC Expert Group mailing list](http://mail.openjdk.java.net/pipermail/jdbc-spec-discuss/).
5+
6+
Reading a bunch of JavaDoc and interfaces can be interesting, but it is not nearly as engaging as having actual running code to play with. To that end, we have uploaded the beginnings of an implementation of ADBA running over standard JDBC, AoJ. AoJ is available for download from [GitHub](https://github.com/oracle/oracle-db-examples/tree/master/java/AoJ) under the Apache license. It should run with any reasonably standard compliant JDBC driver.
7+
8+
AoJ implements only a small part of ADBA, but it is enough to write interesting code. It provides partial implementations of DataSourceFactory, DataSource, Connection, OperationGroup, RowOperation, CountOperation, Transaction and others. These implementations are not complete but there is enough there to write interesting database programs. The code that is there is untested, but it does work to some extent. The saving grace is that you can download the source and improve it: add new features, fix bugs, try out alternate implementations.
9+
10+
Oracle is not proposing AoJ as an open source project. However, because AoJ is released under the Apache license, the Java community can fork the code and create a true open source project with this upload as a base. Oracle developers may contribute when we have time, but this would have to be a Java community effort.
11+
12+
We could have held this code back and worked on it longer. Instead we thought it better to get it to the community as soon as we could. We hope that you agree.
13+
14+
## Sample Code
15+
16+
The following test case should give you some idea of what AoJ can do. It uses the scott/tiger [schema](https://github.com/oracle/dotnet-db-samples/blob/master/schemas/scott.sql). It should run with any JDBC driver connecting to a database with the scott schema.
17+
18+
`````` public void transactionSample() {
19+
DataSourceFactory factory = DataSourceFactory.forName("com.oracle.adbaoverjdbc.DataSourceFactory");
20+
try (DataSource ds = factory.builder()
21+
.url(URL)
22+
.username(“scott")
23+
.password(“tiger")
24+
.build();
25+
Connection conn = ds.getConnection(t -> System.out.println("ERROR: " + t.getMessage()))) {
26+
Transaction trans = conn.transaction();
27+
CompletionStage<Integer> idF = conn.<Integer>rowOperation("select empno, ename from emp where ename = ? for update")
28+
.set("1", "CLARK", AdbaType.VARCHAR)
29+
.collect(Collector.of(
30+
() -> new int[1],
31+
(a, r) -> {a[0] = r.get("empno", Integer.class); },
32+
(l, r) -> null,
33+
a -> a[0])
34+
)
35+
.submit()
36+
.getCompletionStage();
37+
conn.<Long>countOperation("update emp set deptno = ? where empno = ?")
38+
.set("1", 50, AdbaType.INTEGER)
39+
.set("2", idF, AdbaType.INTEGER)
40+
.apply(c -> {
41+
if (c.getCount() != 1L) {
42+
trans.setRollbackOnly();
43+
throw new SqlException("updated wrong number of rows", null, null, -1, null, -1);
44+
}
45+
return c.getCount();
46+
})
47+
.onError(t -> t.printStackTrace())
48+
.submit();
49+
conn.catchErrors();
50+
conn.commitMaybeRollback(trans);
51+
}
52+
ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.MINUTES);
53+
}
54+
55+
Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
/*
2+
* Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.oracle.adbaoverjdbc;
17+
18+
import jdk.incubator.sql2.AdbaConnectionProperty;
19+
import jdk.incubator.sql2.Connection.Lifecycle;
20+
import jdk.incubator.sql2.ConnectionProperty;
21+
import jdk.incubator.sql2.Operation;
22+
import jdk.incubator.sql2.ShardingKey;
23+
import jdk.incubator.sql2.SqlException;
24+
import jdk.incubator.sql2.TransactionOutcome;
25+
import java.sql.DriverManager;
26+
import java.sql.PreparedStatement;
27+
import java.sql.SQLException;
28+
import java.util.HashMap;
29+
import java.util.HashSet;
30+
import java.util.Map;
31+
import java.util.Properties;
32+
import java.util.Set;
33+
import java.util.concurrent.CompletableFuture;
34+
import java.util.concurrent.CompletionStage;
35+
import java.util.concurrent.Executor;
36+
37+
/**
38+
* Connection is a subclass of OperationGroup. The member Operation stuff is mostly
39+
* inherited from OperationGroup. There are a couple of differences. First the
40+
* predecessor for all Connections is an already completed CompletableFuture,
41+
* ROOT. Since ROOT is completed a Connection will begin executing as soon as it
42+
* is submitted. Second, a Connection is not really a member of an OperationGroup
43+
* so the code that handles submitting the Connection is a little different from
44+
* OperationGroup.
45+
*
46+
* A Connection is also contains a java.sql.Connection and has methods to execute
47+
* some JDBC actions. It might be a good idea to move the java.sql.Connection and
48+
* associated actions to a separate class.
49+
*/
50+
class Connection extends OperationGroup<Object, Object> implements jdk.incubator.sql2.Connection {
51+
52+
// STATIC
53+
protected static final CompletionStage<Object> ROOT = CompletableFuture.completedFuture(null);
54+
55+
static jdk.incubator.sql2.Connection newConnection(DataSource ds,
56+
Map<ConnectionProperty, Object> properties) {
57+
return new Connection(ds, properties);
58+
}
59+
60+
// FIELDS
61+
private Lifecycle connectionLifecycle = Lifecycle.NEW;
62+
private final Set<jdk.incubator.sql2.Connection.ConnectionLifecycleListener> lifecycleListeners;
63+
private final DataSource dataSource;
64+
private final Map<ConnectionProperty, Object> properties;
65+
66+
private java.sql.Connection jdbcConnection;
67+
68+
private final Executor executor;
69+
private CompletableFuture<Object> connectionCF;
70+
71+
// CONSTRUCTORS
72+
private Connection(DataSource ds,
73+
Map<ConnectionProperty, Object> properties) {
74+
super(null, null); // hack as _this_ not allowed. See SimpleOperation constructor
75+
this.lifecycleListeners = new HashSet<>();
76+
dataSource = ds;
77+
this.properties = properties;
78+
ConnectionProperty execProp = AdbaConnectionProperty.EXECUTOR;
79+
executor = (Executor) properties.getOrDefault(execProp, execProp.defaultValue());
80+
}
81+
82+
// PUBLIC
83+
@Override
84+
public Operation<Void> connectOperation() {
85+
if (! isHeld()) {
86+
throw new IllegalStateException("TODO");
87+
}
88+
return com.oracle.adbaoverjdbc.SimpleOperation.<Void>newOperation(this, this, this::jdbcConnect);
89+
}
90+
91+
@Override
92+
public Operation<Void> validationOperation(Validation depth) {
93+
if (! isHeld()) {
94+
throw new IllegalStateException("TODO");
95+
}
96+
return com.oracle.adbaoverjdbc.SimpleOperation.<Void>newOperation(this, this, op -> jdbcValidate(op, depth));
97+
}
98+
99+
@Override
100+
public Operation<Void> closeOperation() {
101+
if (! isHeld()) {
102+
throw new IllegalStateException("TODO");
103+
}
104+
return com.oracle.adbaoverjdbc.UnskippableOperation.<Void>newOperation(this, this, this::jdbcClose); //TODO cannot be skipped
105+
}
106+
107+
@Override
108+
public <S, T> jdk.incubator.sql2.OperationGroup<S, T> operationGroup() {
109+
if (!isHeld()) {
110+
throw new IllegalStateException("TODO");
111+
}
112+
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
113+
}
114+
115+
@Override
116+
public Transaction transaction() {
117+
if (! isHeld()) {
118+
throw new IllegalStateException("TODO");
119+
}
120+
return Transaction.createTransaction(this);
121+
}
122+
123+
@Override
124+
public Connection registerLifecycleListener(ConnectionLifecycleListener listener) {
125+
if (!connectionLifecycle.isActive()) {
126+
throw new IllegalStateException("TODO");
127+
}
128+
lifecycleListeners.add(listener);
129+
return this;
130+
}
131+
132+
@Override
133+
public Connection deregisterLifecycleListener(ConnectionLifecycleListener listener) {
134+
if (!connectionLifecycle.isActive()) {
135+
throw new IllegalStateException("TODO");
136+
}
137+
lifecycleListeners.remove(listener);
138+
return this;
139+
}
140+
141+
@Override
142+
public Lifecycle getConnectionLifecycle() {
143+
return connectionLifecycle;
144+
}
145+
146+
@Override
147+
public jdk.incubator.sql2.Connection abort() {
148+
setLifecycle(connectionLifecycle.abort());
149+
this.closeImmediate();
150+
return this;
151+
}
152+
153+
@Override
154+
public Map<ConnectionProperty, Object> getProperties() {
155+
Map<ConnectionProperty, Object> map = new HashMap<>(properties.size());
156+
properties.forEach((k, v) -> {
157+
if (!k.isSensitive()) {
158+
map.put(k, v);
159+
}
160+
});
161+
return map;
162+
}
163+
164+
@Override
165+
public ShardingKey.Builder shardingKeyBuilder() {
166+
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
167+
}
168+
169+
@Override
170+
public jdk.incubator.sql2.Connection activate() {
171+
setLifecycle(connectionLifecycle.activate());
172+
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
173+
}
174+
175+
@Override
176+
public jdk.incubator.sql2.Connection deactivate() {
177+
setLifecycle(connectionLifecycle.deactivate());
178+
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
179+
}
180+
181+
182+
183+
184+
// INTERNAL
185+
protected Connection setLifecycle(Lifecycle next) {
186+
Lifecycle previous = connectionLifecycle;
187+
connectionLifecycle = next;
188+
if (previous != next) {
189+
lifecycleListeners.stream().forEach(l -> l.lifecycleEvent(this, previous, next));
190+
}
191+
return this;
192+
}
193+
194+
Connection closeImmediate() {
195+
try {
196+
if (jdbcConnection != null && !jdbcConnection.isClosed()) {
197+
setLifecycle(connectionLifecycle.abort());
198+
jdbcConnection.abort(executor); // Connection.abort is not supposed to hang
199+
//TODO should call connectionLifecycle.close() when abort completes.
200+
}
201+
}
202+
catch (SQLException ex) {
203+
throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), null, -1);
204+
}
205+
finally {
206+
dataSource.deregisterConnection(this);
207+
}
208+
return this;
209+
}
210+
211+
@Override
212+
protected Executor getExecutor() {
213+
return executor;
214+
}
215+
216+
@Override
217+
jdk.incubator.sql2.Submission<Object> submit(com.oracle.adbaoverjdbc.Operation<Object> op) {
218+
if (op == this) {
219+
// submitting the Connection OperationGroup
220+
connectionCF = (CompletableFuture<Object>)attachErrorHandler(op.follows(ROOT, getExecutor()));
221+
return com.oracle.adbaoverjdbc.Submission.submit(this::cancel, connectionCF);
222+
}
223+
else {
224+
return super.submit(op);
225+
}
226+
}
227+
228+
229+
230+
231+
// JDBC operations. These are all blocking
232+
233+
private Void jdbcConnect(com.oracle.adbaoverjdbc.Operation<Void> op) {
234+
try {
235+
Properties info = (Properties) ((Properties) properties.get(JdbcConnectionProperties.JDBC_CONNECTION_PROPERTIES)).clone();
236+
info.setProperty("user", (String) properties.get(AdbaConnectionProperty.USER));
237+
info.setProperty("password", (String) properties.get(AdbaConnectionProperty.PASSWORD));
238+
String url = (String) properties.get(AdbaConnectionProperty.URL);
239+
System.out.println("DriverManager.getConnection(\"" + url + "\", " + info +")"); //DEBUG
240+
jdbcConnection = DriverManager.getConnection(url, info);
241+
jdbcConnection.setAutoCommit(false);
242+
setLifecycle(Connection.Lifecycle.OPEN);
243+
return null;
244+
}
245+
catch (SQLException ex) {
246+
throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), null, -1);
247+
}
248+
}
249+
250+
private Void jdbcValidate(com.oracle.adbaoverjdbc.Operation<Void> op,
251+
Validation depth) {
252+
try {
253+
switch (depth) {
254+
case COMPLETE:
255+
case SERVER:
256+
int timeoutSeconds = (int) (op.getTimeoutMillis() / 1000L);
257+
System.out.println("Connection.isValid(" + timeoutSeconds + ")"); //DEBUG
258+
if (!jdbcConnection.isValid(timeoutSeconds)) {
259+
throw new SqlException("validation failure", null, null, -1, null, -1);
260+
}
261+
break;
262+
case NETWORK:
263+
case SOCKET:
264+
case LOCAL:
265+
case NONE:
266+
System.out.println("Connection.isClosed"); //DEBUG
267+
if (jdbcConnection.isClosed()) {
268+
throw new SqlException("validation failure", null, null, -1, null, -1);
269+
}
270+
}
271+
return null;
272+
}
273+
catch (SQLException ex) {
274+
throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), null, -1);
275+
}
276+
}
277+
278+
279+
protected <T> T jdbcExecute(com.oracle.adbaoverjdbc.Operation<T> op, String sql) {
280+
try (java.sql.Statement stmt = jdbcConnection.createStatement()) {
281+
int timeoutSeconds = (int) (op.getTimeoutMillis() / 1000L);
282+
if (timeoutSeconds < 0) stmt.setQueryTimeout(timeoutSeconds);
283+
System.out.println("Statement.execute(\"" + sql + "\")"); //DEBUG
284+
stmt.execute(sql);
285+
}
286+
catch (SQLException ex) {
287+
throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), sql, -1);
288+
}
289+
return null;
290+
}
291+
292+
private Void jdbcClose(com.oracle.adbaoverjdbc.Operation<Void> op) {
293+
try {
294+
setLifecycle(connectionLifecycle.close());
295+
if (jdbcConnection != null) {
296+
System.out.println("Connection.close"); //DEBUG
297+
jdbcConnection.close();
298+
}
299+
}
300+
catch (SQLException ex) {
301+
throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), null, -1);
302+
}
303+
finally {
304+
closeImmediate();
305+
setLifecycle(connectionLifecycle.closed());
306+
}
307+
return null;
308+
}
309+
310+
PreparedStatement prepareStatement(String sqlString) throws SQLException {
311+
System.out.println("Connection.prepareStatement(\"" + sqlString + "\")"); //DEBUG
312+
return jdbcConnection.prepareStatement(sqlString);
313+
}
314+
315+
TransactionOutcome jdbcEndTransaction(SimpleOperation<TransactionOutcome> op, Transaction trans) {
316+
try {
317+
if (trans.endWithCommit(this)) {
318+
System.out.println("commit"); //DEBUG
319+
jdbcConnection.commit();
320+
return TransactionOutcome.COMMIT;
321+
}
322+
else {
323+
System.out.println("rollback"); //DEBUG
324+
jdbcConnection.rollback();
325+
return TransactionOutcome.ROLLBACK;
326+
}
327+
}
328+
catch (SQLException ex) {
329+
throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), null, -1);
330+
}
331+
}
332+
333+
}

0 commit comments

Comments
 (0)