mirror of
https://github.com/jlengrand/helidon.git
synced 2026-03-10 08:21:17 +00:00
Helidon DB Client (#657)
* Helidon DB API and implementation for JDBC and MongoDB. Signed-off-by: Tomas Kraus <Tomas.Kraus@oracle.com> Signed-off-by: Tomas Langer <tomas.langer@oracle.com> * Hikari CP metrics Signed-off-by: Tomas Langer <tomas.langer@oracle.com>
This commit is contained in:
69
bom/pom.xml
69
bom/pom.xml
@@ -503,6 +503,75 @@
|
||||
<artifactId>helidon-common-metrics</artifactId>
|
||||
<version>${helidon.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.common</groupId>
|
||||
<artifactId>helidon-common-mapper</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- db client -->
|
||||
<dependency>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient-jdbc</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient-mongodb</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient-mongodb</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient-health</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient-jsonp</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient-metrics</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient-metrics-jdbc</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient-tracing</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient-webserver-jsonp</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- db client examples -->
|
||||
<dependency>
|
||||
<groupId>io.helidon.examples.dbclient</groupId>
|
||||
<artifactId>helidon-examples-dbclient-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- tracing -->
|
||||
<dependency>
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.common.configurable;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
|
||||
/**
|
||||
* Least recently used cache.
|
||||
* This cache has a capacity. When the capacity is reached, the oldest record is removed from the cache when a new one
|
||||
* is added.
|
||||
*
|
||||
* @param <K> type of the keys of the map
|
||||
* @param <V> type of the values of the map
|
||||
*/
|
||||
public final class LruCache<K, V> {
|
||||
/**
|
||||
* Default capacity of the cache: {@value}.
|
||||
*/
|
||||
public static final int DEFAULT_CAPACITY = 10000;
|
||||
|
||||
private final LinkedHashMap<K, V> backingMap = new LinkedHashMap<>();
|
||||
private final ReadWriteLock rwLock = new ReentrantReadWriteLock(true);
|
||||
private final Lock readLock = rwLock.readLock();
|
||||
private final Lock writeLock = rwLock.writeLock();
|
||||
|
||||
private final int capacity;
|
||||
|
||||
private LruCache(Builder<K, V> builder) {
|
||||
this.capacity = builder.capacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new builder.
|
||||
*
|
||||
* @param <K> key type
|
||||
* @param <V> value type
|
||||
* @return a new fluent API builder instance
|
||||
*/
|
||||
public static <K, V> Builder<K, V> builder() {
|
||||
return new Builder<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance with default configuration.
|
||||
*
|
||||
* @param <K> key type
|
||||
* @param <V> value type
|
||||
* @return a new cache instance
|
||||
* @see #DEFAULT_CAPACITY
|
||||
*/
|
||||
public static <K, V> LruCache<K, V> create() {
|
||||
Builder<K, V> builder = builder();
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value from the cache.
|
||||
*
|
||||
* @param key key to retrieve
|
||||
* @return value if present or empty
|
||||
*/
|
||||
public Optional<V> get(K key) {
|
||||
readLock.lock();
|
||||
|
||||
V value;
|
||||
try {
|
||||
value = backingMap.get(key);
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
|
||||
if (null == value) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
writeLock.lock();
|
||||
try {
|
||||
// make sure the value is the last in the map (I do ignore a race here, as it is not significant)
|
||||
// if some other thread moved another record to the front, we just move ours before it
|
||||
|
||||
// TODO this hurts - we just need to move the key to the last position
|
||||
// maybe this should be replaced with a list and a map?
|
||||
value = backingMap.get(key);
|
||||
if (null == value) {
|
||||
return Optional.empty();
|
||||
}
|
||||
backingMap.remove(key);
|
||||
backingMap.put(key, value);
|
||||
|
||||
return Optional.of(value);
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a value from the cache.
|
||||
*
|
||||
* @param key key of the record to remove
|
||||
* @return the value that was mapped to the key, or empty if none was
|
||||
*/
|
||||
public Optional<V> remove(K key) {
|
||||
|
||||
writeLock.lock();
|
||||
try {
|
||||
return Optional.ofNullable(backingMap.remove(key));
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a value to the cache.
|
||||
*
|
||||
* @param key key to add
|
||||
* @param value value to add
|
||||
* @return value that was already mapped or empty if the value was not mapped
|
||||
*/
|
||||
public Optional<V> put(K key, V value) {
|
||||
writeLock.lock();
|
||||
try {
|
||||
V currentValue = backingMap.remove(key);
|
||||
if (null == currentValue) {
|
||||
// need to free space - we did not make the map smaller
|
||||
if (backingMap.size() >= capacity) {
|
||||
Iterator<V> iterator = backingMap.values().iterator();
|
||||
iterator.next();
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
backingMap.put(key, value);
|
||||
return Optional.ofNullable(currentValue);
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Either return a cached value or compute it and cache it.
|
||||
* In case this method is called in parallel for the same key, the value actually present in the map may be from
|
||||
* any of the calls.
|
||||
* This method always returns either the existing value from the map, or the value provided by the supplier. It
|
||||
* never returns a result from another thread's supplier.
|
||||
*
|
||||
* @param key key to check/insert value for
|
||||
* @param valueSupplier supplier called if the value is not yet cached, or is invalid
|
||||
* @return current value from the cache, or computed value from the supplier
|
||||
*/
|
||||
public Optional<V> computeValue(K key, Supplier<Optional<V>> valueSupplier) {
|
||||
// get is properly synchronized
|
||||
Optional<V> currentValue = get(key);
|
||||
if (currentValue.isPresent()) {
|
||||
return currentValue;
|
||||
}
|
||||
Optional<V> newValue = valueSupplier.get();
|
||||
// put is also properly synchronized - nevertheless we may replace the value more then once
|
||||
// if called from parallel threads
|
||||
newValue.ifPresent(theValue -> put(key, theValue));
|
||||
|
||||
return newValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current size of the map.
|
||||
*
|
||||
* @return number of records currently cached
|
||||
*/
|
||||
public int size() {
|
||||
readLock.lock();
|
||||
try {
|
||||
return backingMap.size();
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Capacity of this cache.
|
||||
*
|
||||
* @return configured capacity of this cache
|
||||
*/
|
||||
public int capacity() {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
// for unit testing
|
||||
V directGet(K key) {
|
||||
return backingMap.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent API builder for {@link io.helidon.common.configurable.LruCache}.
|
||||
*
|
||||
* @param <K> type of keys
|
||||
* @param <V> type of values
|
||||
*/
|
||||
public static class Builder<K, V> implements io.helidon.common.Builder<LruCache<K, V>> {
|
||||
private int capacity = DEFAULT_CAPACITY;
|
||||
|
||||
@Override
|
||||
public LruCache<K, V> build() {
|
||||
return new LruCache<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration of this cache from configuration.
|
||||
*
|
||||
* @param config configuration
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public Builder<K, V> config(Config config) {
|
||||
config.get("capacity").asInt().ifPresent(this::capacity);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure capacity of the cache.
|
||||
*
|
||||
* @param capacity maximal number of records in the cache before the oldest one is removed
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public Builder<K, V> capacity(int capacity) {
|
||||
this.capacity = capacity;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.common.configurable;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
/**
|
||||
* Unit test for {@link LruCache}.
|
||||
*/
|
||||
class LruCacheTest {
|
||||
@Test
|
||||
void testCache() {
|
||||
LruCache<String, String> theCache = LruCache.create();
|
||||
String value = "cached";
|
||||
String key = "theKey";
|
||||
String newValue = "not-cached";
|
||||
|
||||
Optional<String> res = theCache.put(key, value);
|
||||
assertThat(res, is(Optional.empty()));
|
||||
res = theCache.get(key);
|
||||
assertThat(res, is(Optional.of(value)));
|
||||
res = theCache.computeValue(key, () -> Optional.of(newValue));
|
||||
assertThat(res, is(Optional.of(value)));
|
||||
res = theCache.remove(key);
|
||||
assertThat(res, is(Optional.of(value)));
|
||||
res = theCache.get(key);
|
||||
assertThat(res, is(Optional.empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCacheComputeValue() {
|
||||
LruCache<String, String> theCache = LruCache.create();
|
||||
String value = "cached";
|
||||
String key = "theKey";
|
||||
String newValue = "not-cached";
|
||||
|
||||
Optional<String> res = theCache.computeValue(key, () -> Optional.of(value));
|
||||
assertThat(res, is(Optional.of(value)));
|
||||
res = theCache.get(key);
|
||||
assertThat(res, is(Optional.of(value)));
|
||||
res = theCache.computeValue(key, () -> Optional.of(newValue));
|
||||
assertThat(res, is(Optional.of(value)));
|
||||
res = theCache.remove(key);
|
||||
assertThat(res, is(Optional.of(value)));
|
||||
res = theCache.get(key);
|
||||
assertThat(res, is(Optional.empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMaxCapacity() {
|
||||
LruCache<Integer, Integer> theCache = LruCache.<Integer, Integer>builder().capacity(10).build();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
theCache.put(i, i);
|
||||
}
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Optional<Integer> integer = theCache.get(i);
|
||||
assertThat(integer, is(Optional.of(i)));
|
||||
}
|
||||
theCache.put(10, 10);
|
||||
Optional<Integer> res = theCache.get(0);
|
||||
assertThat(res, is(Optional.empty()));
|
||||
res = theCache.get(10);
|
||||
assertThat(res, is(Optional.of(10)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLruBehavior() {
|
||||
LruCache<Integer, Integer> theCache = LruCache.<Integer, Integer>builder().capacity(10).build();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
// insert all
|
||||
theCache.put(i, i);
|
||||
}
|
||||
for (int i = 0; i < 10; i++) {
|
||||
// use them in ascending order
|
||||
Optional<Integer> integer = theCache.get(i);
|
||||
assertThat(integer, is(Optional.of(i)));
|
||||
}
|
||||
// now use 0
|
||||
Optional<Integer> value = theCache.get(0);
|
||||
assertThat(value, is(Optional.of(0)));
|
||||
|
||||
theCache.put(10, 10);
|
||||
|
||||
// 0 should be in
|
||||
value = theCache.get(0);
|
||||
assertThat(value, is(Optional.of(0)));
|
||||
|
||||
// 1 should not
|
||||
value = theCache.get(1);
|
||||
assertThat(value, is(Optional.empty()));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.common.reactive;
|
||||
|
||||
import java.util.concurrent.Flow;
|
||||
|
||||
/**
|
||||
* Utilities for Flow API.
|
||||
*/
|
||||
public final class Flows {
|
||||
private Flows() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty publisher.
|
||||
*
|
||||
* @param <T> type of the publisher
|
||||
* @return a new empty publisher that just completes the subscriber
|
||||
*/
|
||||
public static <T> Flow.Publisher<T> emptyPublisher() {
|
||||
return subscriber -> subscriber.onSubscribe(new Flow.Subscription() {
|
||||
@Override
|
||||
public void request(long n) {
|
||||
subscriber.onComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A publisher of a single value.
|
||||
*
|
||||
* @param value value to publish
|
||||
* @param <T> type of the publisher
|
||||
* @return a new publisher that publishes the single value and completes the subscriber
|
||||
*/
|
||||
public static <T> Flow.Publisher<T> singletonPublisher(T value) {
|
||||
return subscriber -> subscriber.onSubscribe(new Flow.Subscription() {
|
||||
@Override
|
||||
public void request(long n) {
|
||||
subscriber.onNext(value);
|
||||
subscriber.onComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.common.reactive;
|
||||
|
||||
import java.util.concurrent.Flow;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* A {@link Flow.Processor} that only maps the source type to target type using a mapping function.
|
||||
*
|
||||
* @param <SOURCE> type of the publisher we subscribe to
|
||||
* @param <TARGET> type of the publisher we expose
|
||||
*/
|
||||
public final class MappingProcessor<SOURCE, TARGET> implements Flow.Processor<SOURCE, TARGET> {
|
||||
private final Function<SOURCE, TARGET> resultMapper;
|
||||
private Flow.Subscriber<? super TARGET> mySubscriber;
|
||||
private Flow.Subscription subscription;
|
||||
|
||||
private MappingProcessor(Function<SOURCE, TARGET> resultMapper) {
|
||||
this.resultMapper = resultMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mapping processor for a mapping function.
|
||||
* @param mappingFunction function that maps source to target (applied for each record)
|
||||
* @param <S> Source type
|
||||
* @param <T> Target type
|
||||
* @return a new mapping processor
|
||||
*/
|
||||
public static <S, T> MappingProcessor<S, T> create(Function<S, T> mappingFunction) {
|
||||
return new MappingProcessor<>(mappingFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribe(Flow.Subscriber<? super TARGET> subscriber) {
|
||||
this.mySubscriber = subscriber;
|
||||
subscriber.onSubscribe(new Flow.Subscription() {
|
||||
@Override
|
||||
public void request(long n) {
|
||||
if (null != subscription) {
|
||||
subscription.request(n);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (null != subscription) {
|
||||
subscription.cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubscribe(Flow.Subscription subscription) {
|
||||
this.subscription = subscription;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(SOURCE item) {
|
||||
mySubscriber.onNext(resultMapper.apply(item));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
mySubscriber.onError(throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
mySubscriber.onComplete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.common.reactive;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A completion stage that allows processing of cases when the element
|
||||
* is present and when not.
|
||||
*
|
||||
* @param <T> return type of the asynchronous operation
|
||||
*/
|
||||
public interface OptionalCompletionStage<T> extends CompletionStage<Optional<T>> {
|
||||
|
||||
/**
|
||||
* Returns a new {@code OptionalCompletionStage} that, when this stage completes
|
||||
* normally and returns {@code null}, executes the given action.
|
||||
*
|
||||
* @param action the action to perform before completing the
|
||||
* returned {@code OptionalCompletionStage}
|
||||
* @return the new {@code OptionalCompletionStage}
|
||||
*/
|
||||
OptionalCompletionStage<T> onEmpty(Runnable action);
|
||||
|
||||
/**
|
||||
* Returns a new {@code OptionalCompletionStage} that, when this stage completes
|
||||
* normally and returns non-{@code null}, is executed with this stage's
|
||||
* result as the argument to the supplied action.
|
||||
*
|
||||
* @param action the action to perform before completing the
|
||||
* returned {@code OptionalCompletionStage}
|
||||
* @return the new {@code OptionalCompletionStage}
|
||||
*/
|
||||
OptionalCompletionStage<T> onValue(Consumer<? super T> action);
|
||||
|
||||
/**
|
||||
* Creates a new instance of the completion stage that allows processing of cases when the element
|
||||
* is present and when not.
|
||||
*
|
||||
* @param <T> return type of the asynchronous operation
|
||||
* @param originalStage source completion stage instance
|
||||
* @return the new {@code OptionalCompletionStage}
|
||||
*/
|
||||
static <T> OptionalCompletionStage<T> create(CompletionStage<Optional<T>> originalStage) {
|
||||
return new OptionalCompletionStageImpl<>(originalStage);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.common.reactive;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Internal implementation of the completion stage that allows processing of cases when the element is present and when
|
||||
* not.
|
||||
*
|
||||
* @param <T> return type of the asynchronous operation
|
||||
*/
|
||||
class OptionalCompletionStageImpl<T> implements OptionalCompletionStage<T> {
|
||||
|
||||
private final CompletionStage<Optional<T>> originalStage;
|
||||
|
||||
OptionalCompletionStageImpl(CompletionStage<Optional<T>> originalStag) {
|
||||
this.originalStage = originalStag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionalCompletionStage<T> onEmpty(Runnable action) {
|
||||
originalStage.thenAccept(original -> {
|
||||
if (!original.isPresent()) {
|
||||
action.run();
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionalCompletionStage<T> onValue(Consumer<? super T> action) {
|
||||
originalStage.thenAccept(original -> {
|
||||
original.ifPresent(action);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletionStage<U> thenApply(Function<? super Optional<T>, ? extends U> fn) {
|
||||
return originalStage.thenApply(fn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletionStage<U> thenApplyAsync(Function<? super Optional<T>, ? extends U> fn) {
|
||||
return originalStage.thenApplyAsync(fn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletionStage<U> thenApplyAsync(Function<? super Optional<T>, ? extends U> fn,
|
||||
Executor executor) {
|
||||
return originalStage.thenApplyAsync(fn, executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> thenAccept(Consumer<? super Optional<T>> action) {
|
||||
return originalStage.thenAccept(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> thenAcceptAsync(Consumer<? super Optional<T>> action) {
|
||||
return originalStage.thenAcceptAsync(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> thenAcceptAsync(Consumer<? super Optional<T>> action,
|
||||
Executor executor) {
|
||||
return originalStage.thenAcceptAsync(action, executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> thenRun(Runnable action) {
|
||||
return originalStage.thenRun(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> thenRunAsync(Runnable action) {
|
||||
return originalStage.thenRunAsync(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> thenRunAsync(Runnable action, Executor executor) {
|
||||
return originalStage.thenRunAsync(action, executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U, V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,
|
||||
BiFunction<? super Optional<T>, ? super U, ? extends V> fn) {
|
||||
return originalStage.thenCombine(other, fn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U, V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,
|
||||
BiFunction<? super Optional<T>, ? super U, ? extends V> fn) {
|
||||
return originalStage.thenCombineAsync(other, fn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U, V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,
|
||||
BiFunction<? super Optional<T>, ? super U, ? extends V> fn,
|
||||
Executor executor) {
|
||||
return originalStage.thenCombineAsync(other, fn, executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,
|
||||
BiConsumer<? super Optional<T>, ? super U> action) {
|
||||
return originalStage.thenAcceptBoth(other, action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,
|
||||
BiConsumer<? super Optional<T>, ? super U> action) {
|
||||
return originalStage.thenAcceptBothAsync(other, action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,
|
||||
BiConsumer<? super Optional<T>, ? super U> action,
|
||||
Executor executor) {
|
||||
return originalStage.thenAcceptBothAsync(other, action, executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> runAfterBoth(CompletionStage<?> other, Runnable action) {
|
||||
return originalStage.runAfterBoth(other, action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action) {
|
||||
return originalStage.runAfterBothAsync(other, action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action, Executor executor) {
|
||||
return originalStage.runAfterBothAsync(other, action, executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletionStage<U> applyToEither(CompletionStage<? extends Optional<T>> other,
|
||||
Function<? super Optional<T>, U> fn) {
|
||||
return originalStage.applyToEither(other, fn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends Optional<T>> other,
|
||||
Function<? super Optional<T>, U> fn) {
|
||||
return originalStage.applyToEitherAsync(other, fn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends Optional<T>> other,
|
||||
Function<? super Optional<T>, U> fn,
|
||||
Executor executor) {
|
||||
return originalStage.applyToEitherAsync(other, fn, executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> acceptEither(CompletionStage<? extends Optional<T>> other,
|
||||
Consumer<? super Optional<T>> action) {
|
||||
return originalStage.acceptEither(other, action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends Optional<T>> other,
|
||||
Consumer<? super Optional<T>> action) {
|
||||
return originalStage.acceptEitherAsync(other, action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends Optional<T>> other,
|
||||
Consumer<? super Optional<T>> action,
|
||||
Executor executor) {
|
||||
return originalStage.acceptEitherAsync(other, action, executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> runAfterEither(CompletionStage<?> other, Runnable action) {
|
||||
return originalStage.runAfterEither(other, action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action) {
|
||||
return originalStage.runAfterEitherAsync(other, action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,
|
||||
Runnable action,
|
||||
Executor executor) {
|
||||
return originalStage.runAfterEitherAsync(other, action, executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletionStage<U> thenCompose(Function<? super Optional<T>, ? extends CompletionStage<U>> fn) {
|
||||
return originalStage.thenCompose(fn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletionStage<U> thenComposeAsync(Function<? super Optional<T>, ? extends CompletionStage<U>> fn) {
|
||||
return originalStage.thenComposeAsync(fn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletionStage<U> thenComposeAsync(Function<? super Optional<T>, ? extends CompletionStage<U>> fn,
|
||||
Executor executor) {
|
||||
return originalStage.thenComposeAsync(fn, executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Optional<T>> exceptionally(Function<Throwable, ? extends Optional<T>> fn) {
|
||||
return originalStage.exceptionally(fn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Optional<T>> whenComplete(BiConsumer<? super Optional<T>, ? super Throwable> action) {
|
||||
return originalStage.whenComplete(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Optional<T>> whenCompleteAsync(BiConsumer<? super Optional<T>, ? super Throwable> action) {
|
||||
return originalStage.whenCompleteAsync(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Optional<T>> whenCompleteAsync(BiConsumer<? super Optional<T>, ? super Throwable> action,
|
||||
Executor executor) {
|
||||
return originalStage.whenCompleteAsync(action, executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletionStage<U> handle(BiFunction<? super Optional<T>, Throwable, ? extends U> fn) {
|
||||
return originalStage.handle(fn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletionStage<U> handleAsync(BiFunction<? super Optional<T>, Throwable, ? extends U> fn) {
|
||||
return originalStage.handleAsync(fn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletionStage<U> handleAsync(BiFunction<? super Optional<T>, Throwable, ? extends U> fn,
|
||||
Executor executor) {
|
||||
return originalStage.handleAsync(fn, executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<T>> toCompletableFuture() {
|
||||
return originalStage.toCompletableFuture();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,7 @@
|
||||
package io.helidon.common.reactive;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@@ -62,8 +63,9 @@ public interface Single<T> extends Subscribable<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes this {@link Single} instance as a {@link CompletionStage}. Note that if this {@link Single} completes without a
|
||||
* value, the resulting {@link CompletionStage} will be completed exceptionally with an {@link IllegalStateException}
|
||||
* Exposes this {@link Single} instance as a {@link CompletionStage}.
|
||||
* Note that if this {@link Single} completes without a value, the resulting {@link CompletionStage} will be completed
|
||||
* exceptionally with an {@link IllegalStateException}
|
||||
*
|
||||
* @return CompletionStage
|
||||
*/
|
||||
@@ -79,6 +81,26 @@ public interface Single<T> extends Subscribable<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes this {@link Single} instance as a {@link CompletionStage} with {@code Optional<T>} return type
|
||||
* of the asynchronous operation.
|
||||
* Note that if this {@link Single} completes without a value, the resulting {@link CompletionStage} will be completed
|
||||
* exceptionally with an {@link IllegalStateException}
|
||||
*
|
||||
* @return CompletionStage
|
||||
*/
|
||||
default CompletionStage<Optional<T>> toOptionalStage() {
|
||||
try {
|
||||
SingleToOptionalFuture<T> subscriber = new SingleToOptionalFuture<>();
|
||||
this.subscribe(subscriber);
|
||||
return subscriber;
|
||||
} catch (Throwable ex) {
|
||||
CompletableFuture<Optional<T>> future = new CompletableFuture<>();
|
||||
future.completeExceptionally(ex);
|
||||
return future;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Short-hand for {@code toFuture().toCompletableFuture().get()}.
|
||||
* @return T
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.common.reactive;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Flow.Subscriber;
|
||||
import java.util.concurrent.Flow.Subscription;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* {@link io.helidon.common.reactive.Single} exposed as a {@link java.util.concurrent.CompletableFuture}.
|
||||
*/
|
||||
class SingleToOptionalFuture<T> extends CompletableFuture<Optional<T>> implements Subscriber<T> {
|
||||
|
||||
private final AtomicReference<Subscription> ref = new AtomicReference<>();
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
boolean cancelled = super.cancel(mayInterruptIfRunning);
|
||||
if (cancelled) {
|
||||
Subscription s = ref.getAndSet(null);
|
||||
if (s != null) {
|
||||
s.cancel();
|
||||
}
|
||||
}
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubscribe(Subscription next) {
|
||||
Subscription current = ref.getAndSet(next);
|
||||
Objects.requireNonNull(next, "Subscription cannot be null");
|
||||
if (current != null) {
|
||||
next.cancel();
|
||||
current.cancel();
|
||||
} else {
|
||||
next.request(Long.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(T item) {
|
||||
Subscription s = ref.getAndSet(null);
|
||||
if (s != null) {
|
||||
super.complete(Optional.ofNullable(item));
|
||||
s.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable ex) {
|
||||
if (ref.getAndSet(null) != null) {
|
||||
super.completeExceptionally(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
if (ref.getAndSet(null) != null) {
|
||||
super.complete(Optional.empty());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean complete(Optional<T> value) {
|
||||
throw new UnsupportedOperationException("This future cannot be completed manually");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean completeExceptionally(Throwable ex) {
|
||||
throw new UnsupportedOperationException("This future cannot be completed manually");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,6 +24,7 @@ import io.helidon.config.spi.ConfigNode;
|
||||
import io.helidon.config.spi.ConfigNode.ObjectNode;
|
||||
import io.helidon.config.spi.TestingConfigSource;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.helidon.config.ConfigTest.waitForAssert;
|
||||
@@ -175,7 +176,13 @@ public class ConfigSupplierTest {
|
||||
is(ConfigValues.simpleValue("NEW item 1")));
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
// TODO cause of intermittent test failures:
|
||||
/*
|
||||
Tests in error:
|
||||
ConfigSupplierTest.testSupplierFromMissingToListNode:209->lambda$testSupplierFromMissingToListNode$16:216 » IllegalState
|
||||
*/
|
||||
public void testSupplierFromMissingToListNode() throws InterruptedException {
|
||||
// config source
|
||||
TestingConfigSource configSource = TestingConfigSource.builder().build();
|
||||
|
||||
51
dbclient/common/pom.xml
Normal file
51
dbclient/common/pom.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
Licensed 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>helidon-dbclient-project</artifactId>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>helidon-dbclient-common</artifactId>
|
||||
<name>Helidon DB Client Common</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.common</groupId>
|
||||
<artifactId>helidon-common-configurable</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.common;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
|
||||
import io.helidon.dbclient.DbExecute;
|
||||
import io.helidon.dbclient.DbStatementDml;
|
||||
import io.helidon.dbclient.DbStatementGeneric;
|
||||
import io.helidon.dbclient.DbStatementGet;
|
||||
import io.helidon.dbclient.DbStatementQuery;
|
||||
import io.helidon.dbclient.DbStatementType;
|
||||
import io.helidon.dbclient.DbStatements;
|
||||
|
||||
/**
|
||||
* Implements methods that do not require implementation for each provider.
|
||||
*/
|
||||
public abstract class AbstractDbExecute implements DbExecute {
|
||||
private final DbStatements statements;
|
||||
|
||||
/**
|
||||
* Create an instance with configured statements.
|
||||
*
|
||||
* @param statements statements to obtains named statements, esp. for {@link #statementText(String)}.
|
||||
*/
|
||||
protected AbstractDbExecute(DbStatements statements) {
|
||||
this.statements = statements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a statement text based on the statement name.
|
||||
* This is a utility method that probably would use {@link io.helidon.dbclient.DbStatements} to retrieve the named statements.
|
||||
*
|
||||
* @param name name of the statement
|
||||
* @return statement text
|
||||
*/
|
||||
protected String statementText(String name) {
|
||||
return statements.statement(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementQuery createNamedQuery(String statementName) {
|
||||
return createNamedQuery(statementName, statementText(statementName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementQuery createQuery(String statement) {
|
||||
return createNamedQuery(generateName(DbStatementType.QUERY, statement), statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementGet createNamedGet(String statementName) {
|
||||
return createNamedGet(statementName, statementText(statementName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementGet createGet(String statement) {
|
||||
return createNamedGet(generateName(DbStatementType.GET, statement), statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementDml createNamedInsert(String statementName) {
|
||||
return createNamedInsert(statementName, statementText(statementName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementDml createInsert(String statement) {
|
||||
return createNamedInsert(generateName(DbStatementType.INSERT, statement), statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementDml createNamedUpdate(String statementName) {
|
||||
return createNamedUpdate(statementName, statementText(statementName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementDml createUpdate(String statement) {
|
||||
return createNamedUpdate(generateName(DbStatementType.UPDATE, statement), statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementDml createNamedDelete(String statementName) {
|
||||
return createNamedDelete(statementName, statementText(statementName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementDml createDelete(String statement) {
|
||||
return createNamedDelete(generateName(DbStatementType.DELETE, statement), statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementDml createNamedDmlStatement(String statementName) {
|
||||
return createNamedDmlStatement(statementName, statementText(statementName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementDml createDmlStatement(String statement) {
|
||||
return createNamedDmlStatement(generateName(DbStatementType.DML, statement), statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementGeneric createNamedStatement(String statementName) {
|
||||
return createNamedStatement(statementName, statementText(statementName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementGeneric createStatement(String statement) {
|
||||
return createNamedStatement(generateName(DbStatementType.UNKNOWN, statement), statement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a name for a statement.
|
||||
* The default implementation uses {@code SHA-256} so the same name is always
|
||||
* returned for the same statement.
|
||||
* <p>
|
||||
* As there is always a small risk of duplicity, named statements are recommended!
|
||||
*
|
||||
* @param type type of the statement
|
||||
* @param statement statement that it going to be executed
|
||||
* @return name of the statement
|
||||
*/
|
||||
protected String generateName(DbStatementType type, String statement) {
|
||||
String sha256;
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
digest.update(statement.getBytes(StandardCharsets.UTF_8));
|
||||
sha256 = Base64.getEncoder().encodeToString(digest.digest());
|
||||
} catch (NoSuchAlgorithmException ignored) {
|
||||
return "sha256failed";
|
||||
}
|
||||
return type.prefix() + '_' + sha256;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.common;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import io.helidon.common.context.Context;
|
||||
import io.helidon.common.context.Contexts;
|
||||
import io.helidon.common.mapper.MapperManager;
|
||||
import io.helidon.dbclient.DbInterceptor;
|
||||
import io.helidon.dbclient.DbInterceptorContext;
|
||||
import io.helidon.dbclient.DbMapperManager;
|
||||
import io.helidon.dbclient.DbStatement;
|
||||
import io.helidon.dbclient.DbStatementType;
|
||||
|
||||
/**
|
||||
* Common statement methods and fields.
|
||||
*
|
||||
* @param <S> type of a subclass
|
||||
* @param <R> the result type of the statement as returned by {@link #execute()}
|
||||
*/
|
||||
public abstract class AbstractStatement<S extends DbStatement<S, R>, R> implements DbStatement<S, R> {
|
||||
|
||||
/** Local logger instance. */
|
||||
private static final Logger LOGGER = Logger.getLogger(AbstractStatement.class.getName());
|
||||
|
||||
private ParamType paramType = ParamType.UNKNOWN;
|
||||
private StatementParameters parameters;
|
||||
private final DbStatementType dbStatementType;
|
||||
private final String statementName;
|
||||
private final String statement;
|
||||
private final DbMapperManager dbMapperManager;
|
||||
private final MapperManager mapperManager;
|
||||
private final InterceptorSupport interceptors;
|
||||
|
||||
/**
|
||||
* Statement that handles parameters.
|
||||
*
|
||||
* @param dbStatementType type of this statement
|
||||
* @param statementName name of this statement
|
||||
* @param statement text of this statement
|
||||
* @param dbMapperManager db mapper manager to use when mapping types to parameters
|
||||
* @param mapperManager mapper manager to use when mapping results
|
||||
* @param interceptors interceptors to be executed
|
||||
*/
|
||||
protected AbstractStatement(DbStatementType dbStatementType,
|
||||
String statementName,
|
||||
String statement,
|
||||
DbMapperManager dbMapperManager,
|
||||
MapperManager mapperManager,
|
||||
InterceptorSupport interceptors) {
|
||||
this.dbStatementType = dbStatementType;
|
||||
this.statementName = statementName;
|
||||
this.statement = statement;
|
||||
this.dbMapperManager = dbMapperManager;
|
||||
this.mapperManager = mapperManager;
|
||||
this.interceptors = interceptors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<R> execute() {
|
||||
CompletableFuture<Long> queryFuture = new CompletableFuture<>();
|
||||
CompletableFuture<Void> statementFuture = new CompletableFuture<>();
|
||||
DbInterceptorContext dbContext = DbInterceptorContext.create(dbType())
|
||||
.resultFuture(queryFuture)
|
||||
.statementFuture(statementFuture);
|
||||
|
||||
update(dbContext);
|
||||
CompletionStage<DbInterceptorContext> dbContextFuture = invokeInterceptors(dbContext);
|
||||
return doExecute(dbContextFuture, statementFuture, queryFuture);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke all interceptors.
|
||||
*
|
||||
* @param dbContext initial interceptor context
|
||||
* @return future with the result of interceptors processing
|
||||
*/
|
||||
CompletionStage<DbInterceptorContext> invokeInterceptors(DbInterceptorContext dbContext) {
|
||||
CompletableFuture<DbInterceptorContext> result = CompletableFuture.completedFuture(dbContext);
|
||||
|
||||
dbContext.context(Contexts.context().orElseGet(Context::create));
|
||||
|
||||
for (DbInterceptor interceptor : interceptors.interceptors(statementType(), statementName())) {
|
||||
result = result.thenCompose(interceptor::statement);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of this statement.
|
||||
*
|
||||
* @return statement type
|
||||
*/
|
||||
protected DbStatementType statementType() {
|
||||
return dbStatementType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the statement against the database.
|
||||
*
|
||||
* @param dbContext future that completes after all interceptors are invoked
|
||||
* @param statementFuture future that should complete when the statement finishes execution
|
||||
* @param queryFuture future that should complete when the result set is fully read (if one exists),
|
||||
* otherwise complete same as statementFuture
|
||||
* @return result of this db statement.
|
||||
*/
|
||||
protected abstract CompletionStage<R> doExecute(CompletionStage<DbInterceptorContext> dbContext,
|
||||
CompletableFuture<Void> statementFuture,
|
||||
CompletableFuture<Long> queryFuture);
|
||||
|
||||
/**
|
||||
* Type of this database to use in interceptor context.
|
||||
*
|
||||
* @return type of this db
|
||||
*/
|
||||
protected abstract String dbType();
|
||||
|
||||
@Override
|
||||
public S params(List<?> parameters) {
|
||||
Objects.requireNonNull(parameters, "Parameters cannot be null (may be an empty list)");
|
||||
|
||||
initParameters(ParamType.INDEXED);
|
||||
this.parameters.params(parameters);
|
||||
|
||||
return me();
|
||||
}
|
||||
|
||||
@Override
|
||||
public S params(Map<String, ?> parameters) {
|
||||
initParameters(ParamType.NAMED);
|
||||
this.parameters.params(parameters);
|
||||
return me();
|
||||
}
|
||||
|
||||
@Override
|
||||
public S namedParam(Object parameters) {
|
||||
initParameters(ParamType.NAMED);
|
||||
this.parameters.namedParam(parameters);
|
||||
return me();
|
||||
}
|
||||
|
||||
@Override
|
||||
public S indexedParam(Object parameters) {
|
||||
initParameters(ParamType.INDEXED);
|
||||
this.parameters.indexedParam(parameters);
|
||||
return me();
|
||||
}
|
||||
|
||||
@Override
|
||||
public S addParam(Object parameter) {
|
||||
initParameters(ParamType.INDEXED);
|
||||
this.parameters.addParam(parameter);
|
||||
return me();
|
||||
}
|
||||
|
||||
@Override
|
||||
public S addParam(String name, Object parameter) {
|
||||
initParameters(ParamType.NAMED);
|
||||
this.parameters.addParam(name, parameter);
|
||||
return me();
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of parameters of this statement.
|
||||
*
|
||||
* @return indexed or named, or unknown in case it could not be yet defined
|
||||
*/
|
||||
protected ParamType paramType() {
|
||||
return paramType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Db mapper manager.
|
||||
*
|
||||
* @return mapper manager for DB types
|
||||
*/
|
||||
protected DbMapperManager dbMapperManager() {
|
||||
return dbMapperManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapper manager.
|
||||
*
|
||||
* @return generic mapper manager
|
||||
*/
|
||||
protected MapperManager mapperManager() {
|
||||
return mapperManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the named parameters of this statement.
|
||||
*
|
||||
* @return name parameter map
|
||||
* @throws java.lang.IllegalStateException in case this statement is using indexed parameters
|
||||
*/
|
||||
protected Map<String, Object> namedParams() {
|
||||
initParameters(ParamType.NAMED);
|
||||
return parameters.namedParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the indexed parameters of this statement.
|
||||
*
|
||||
* @return parameter list
|
||||
* @throws java.lang.IllegalStateException in case this statement is using named parameters
|
||||
*/
|
||||
protected List<Object> indexedParams() {
|
||||
initParameters(ParamType.INDEXED);
|
||||
return parameters.indexedParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* Statement name.
|
||||
*
|
||||
* @return name of this statement (never null, may be generated)
|
||||
*/
|
||||
protected String statementName() {
|
||||
return statementName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Statement text.
|
||||
*
|
||||
* @return text of this statement
|
||||
*/
|
||||
protected String statement() {
|
||||
return statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the interceptor context with the statement name, statement and
|
||||
* statement parameters.
|
||||
*
|
||||
* @param dbContext interceptor context
|
||||
*/
|
||||
protected void update(DbInterceptorContext dbContext) {
|
||||
dbContext.statementName(statementName);
|
||||
initParameters(ParamType.INDEXED);
|
||||
|
||||
if (paramType == ParamType.NAMED) {
|
||||
dbContext.statement(statement, parameters.namedParams());
|
||||
} else {
|
||||
dbContext.statement(statement, parameters.indexedParams());
|
||||
}
|
||||
dbContext.statementType(statementType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this builder cast to the correct type.
|
||||
*
|
||||
* @return this as type extending this class
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected S me() {
|
||||
return (S) this;
|
||||
}
|
||||
|
||||
private void initParameters(ParamType type) {
|
||||
if (this.paramType != ParamType.UNKNOWN) {
|
||||
// already initialized
|
||||
return;
|
||||
}
|
||||
switch (type) {
|
||||
case NAMED:
|
||||
this.paramType = ParamType.NAMED;
|
||||
this.parameters = new NamedStatementParameters(dbMapperManager);
|
||||
break;
|
||||
case INDEXED:
|
||||
case UNKNOWN:
|
||||
default:
|
||||
this.paramType = ParamType.INDEXED;
|
||||
this.parameters = new IndexedStatementParameters(dbMapperManager);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.common;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.helidon.dbclient.DbClientException;
|
||||
import io.helidon.dbclient.DbMapperManager;
|
||||
|
||||
/**
|
||||
* Statement with indexed parameters.
|
||||
*/
|
||||
class IndexedStatementParameters implements StatementParameters {
|
||||
|
||||
private final List<Object> parameters = new LinkedList<>();
|
||||
private final DbMapperManager dbMapperManager;
|
||||
|
||||
IndexedStatementParameters(DbMapperManager dbMapperManager) {
|
||||
this.dbMapperManager = dbMapperManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatementParameters params(List<?> parameters) {
|
||||
this.parameters.clear();
|
||||
this.parameters.addAll(parameters);
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> StatementParameters indexedParam(T parameters) {
|
||||
Class<T> theClass = (Class<T>) parameters.getClass();
|
||||
|
||||
params(dbMapperManager.toIndexedParameters(parameters, theClass));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> indexedParams() {
|
||||
return this.parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatementParameters addParam(Object parameter) {
|
||||
parameters.add(parameter);
|
||||
return this;
|
||||
}
|
||||
|
||||
private static final String CANT_USE_INDEXED_PARAMS
|
||||
= "This is a statement with indexed parameters, cannot use named parameters.";
|
||||
|
||||
@Override
|
||||
public StatementParameters params(Map<String, ?> parameters) {
|
||||
throw new DbClientException(CANT_USE_INDEXED_PARAMS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> StatementParameters namedParam(T parameters) {
|
||||
throw new DbClientException(CANT_USE_INDEXED_PARAMS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatementParameters addParam(String name, Object parameter) {
|
||||
throw new DbClientException(CANT_USE_INDEXED_PARAMS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> namedParams() {
|
||||
throw new DbClientException(CANT_USE_INDEXED_PARAMS);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.common;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.helidon.common.configurable.LruCache;
|
||||
import io.helidon.dbclient.DbInterceptor;
|
||||
import io.helidon.dbclient.DbStatementType;
|
||||
|
||||
/**
|
||||
* Support for interceptors.
|
||||
*/
|
||||
public interface InterceptorSupport {
|
||||
|
||||
/**
|
||||
* Get a list of interceptors to be executed for the specified statement.
|
||||
*
|
||||
* @param dbStatementType Type of the statement
|
||||
* @param statementName Name of the statement (unnamed statements should have a name generated, see
|
||||
* {@link AbstractDbExecute#generateName(io.helidon.dbclient.DbStatementType, String)}
|
||||
* @return list of interceptors to executed for the defined type and name (may be empty)
|
||||
* @see io.helidon.dbclient.DbInterceptor
|
||||
*/
|
||||
List<DbInterceptor> interceptors(DbStatementType dbStatementType, String statementName);
|
||||
|
||||
/**
|
||||
* Create a new fluent API builder.
|
||||
*
|
||||
* @return a builder instance
|
||||
*/
|
||||
static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent API builder for {@link io.helidon.dbclient.common.InterceptorSupport}.
|
||||
*/
|
||||
final class Builder implements io.helidon.common.Builder<InterceptorSupport> {
|
||||
private final List<DbInterceptor> interceptors = new LinkedList<>();
|
||||
private final Map<DbStatementType, List<DbInterceptor>> typeInterceptors = new EnumMap<>(DbStatementType.class);
|
||||
private final Map<String, List<DbInterceptor>> namedStatementInterceptors = new HashMap<>();
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterceptorSupport build() {
|
||||
// the result must be immutable (if somebody modifies the builder, the behavior must not change)
|
||||
List<DbInterceptor> interceptors = new LinkedList<>(this.interceptors);
|
||||
final Map<DbStatementType, List<DbInterceptor>> typeInterceptors = new EnumMap<>(this.typeInterceptors);
|
||||
final Map<String, List<DbInterceptor>> namedStatementInterceptors = new HashMap<>(this.namedStatementInterceptors);
|
||||
|
||||
final LruCache<CacheKey, List<DbInterceptor>> cachedInterceptors = LruCache.create();
|
||||
return new InterceptorSupport() {
|
||||
@Override
|
||||
public List<DbInterceptor> interceptors(DbStatementType dbStatementType, String statementName) {
|
||||
// order is defined in DbInterceptor interface
|
||||
return cachedInterceptors.computeValue(new CacheKey(dbStatementType, statementName), () -> {
|
||||
List<DbInterceptor> result = new LinkedList<>();
|
||||
addAll(result, namedStatementInterceptors.get(statementName));
|
||||
addAll(result, typeInterceptors.get(dbStatementType));
|
||||
result.addAll(interceptors);
|
||||
return Optional.of(Collections.unmodifiableList(result));
|
||||
}).orElseGet(List::of);
|
||||
}
|
||||
|
||||
private void addAll(List<DbInterceptor> result, List<DbInterceptor> dbInterceptors) {
|
||||
if (null == dbInterceptors) {
|
||||
return;
|
||||
}
|
||||
result.addAll(dbInterceptors);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Builder add(DbInterceptor interceptor) {
|
||||
this.interceptors.add(interceptor);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder add(DbInterceptor interceptor, String... statementNames) {
|
||||
for (String statementName : statementNames) {
|
||||
this.namedStatementInterceptors.computeIfAbsent(statementName, theName -> new LinkedList<>())
|
||||
.add(interceptor);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder add(DbInterceptor interceptor, DbStatementType... dbStatementTypes) {
|
||||
for (DbStatementType dbStatementType : dbStatementTypes) {
|
||||
this.typeInterceptors.computeIfAbsent(dbStatementType, theType -> new LinkedList<>())
|
||||
.add(interceptor);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private static final class CacheKey {
|
||||
private final DbStatementType type;
|
||||
private final String statementName;
|
||||
|
||||
private CacheKey(DbStatementType type, String statementName) {
|
||||
this.type = type;
|
||||
this.statementName = statementName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof CacheKey)) {
|
||||
return false;
|
||||
}
|
||||
CacheKey cacheKey = (CacheKey) o;
|
||||
return (type == cacheKey.type)
|
||||
&& statementName.equals(cacheKey.statementName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(type, statementName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.common;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.helidon.dbclient.DbClientException;
|
||||
import io.helidon.dbclient.DbMapperManager;
|
||||
|
||||
/**
|
||||
* Statement with indexed parameters.
|
||||
*/
|
||||
class NamedStatementParameters implements StatementParameters {
|
||||
|
||||
private final Map<String, Object> parameters = new HashMap<>();
|
||||
private final DbMapperManager dbMapperManager;
|
||||
|
||||
NamedStatementParameters(DbMapperManager dbMapperManager) {
|
||||
this.dbMapperManager = dbMapperManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatementParameters params(Map<String, ?> parameters) {
|
||||
this.parameters.clear();
|
||||
this.parameters.putAll(parameters);
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> StatementParameters namedParam(T parameters) {
|
||||
Class<T> theClass = (Class<T>) parameters.getClass();
|
||||
|
||||
return params(dbMapperManager.toNamedParameters(parameters, theClass));
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatementParameters addParam(String name, Object parameter) {
|
||||
this.parameters.put(name, parameter);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> namedParams() {
|
||||
return this.parameters;
|
||||
}
|
||||
|
||||
private static final String CANT_USE_NAMED_PARAMS
|
||||
= "This is a statement with named parameters, cannot use indexed parameters.";
|
||||
|
||||
@Override
|
||||
public StatementParameters params(List<?> parameters) {
|
||||
throw new DbClientException(CANT_USE_NAMED_PARAMS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> StatementParameters indexedParam(T parameters) {
|
||||
throw new DbClientException(CANT_USE_NAMED_PARAMS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatementParameters addParam(Object parameter) {
|
||||
throw new DbClientException(CANT_USE_NAMED_PARAMS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> indexedParams() {
|
||||
throw new DbClientException(CANT_USE_NAMED_PARAMS);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.common;
|
||||
|
||||
/**
|
||||
* Type of statement parameters.
|
||||
*/
|
||||
public enum ParamType {
|
||||
|
||||
/**
|
||||
* Indexed values to be passed to the statement in order.
|
||||
* In JDBC, this is used for statements that use {@code ?} as a placeholder
|
||||
* for parameters.
|
||||
*/
|
||||
INDEXED,
|
||||
|
||||
/**
|
||||
* Named values to be passed to the statement by name.
|
||||
* Unless the underlying database directly supports named parameters,
|
||||
* we use {@code :name} in the statement text to represent
|
||||
* a named parameter.
|
||||
*/
|
||||
NAMED,
|
||||
|
||||
/**
|
||||
* Statement type is not known.
|
||||
*/
|
||||
UNKNOWN
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.common;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Db statement that does not support execution, only parameters.
|
||||
*/
|
||||
interface StatementParameters {
|
||||
|
||||
/**
|
||||
* Configure parameters from a {@link java.util.List} by order.
|
||||
* The statement must use indexed parameters and configure them by order in the provided array.
|
||||
*
|
||||
* @param parameters ordered parameters to set on this statement, never null
|
||||
* @return updated db statement
|
||||
*/
|
||||
StatementParameters params(List<?> parameters);
|
||||
|
||||
/**
|
||||
* Configure parameters from an array by order.
|
||||
* The statement must use indexed parameters and configure them by order in the provided array.
|
||||
*
|
||||
* @param parameters ordered parameters to set on this statement
|
||||
* @param <T> type of the array
|
||||
* @return updated db statement
|
||||
*/
|
||||
default <T> StatementParameters params(T... parameters) {
|
||||
return params(Arrays.asList(parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure named parameters.
|
||||
* The statement must use named parameters and configure them from the provided map.
|
||||
*
|
||||
* @param parameters named parameters to set on this statement
|
||||
* @return updated db statement
|
||||
*/
|
||||
StatementParameters params(Map<String, ?> parameters);
|
||||
|
||||
/**
|
||||
* Configure parameters using {@link Object} instance with registered mapper.
|
||||
* The statement must use named parameters and configure them from the map provided by mapper.
|
||||
*
|
||||
* @param parameters {@link Object} instance containing parameters
|
||||
* @param <T> type of the parameters
|
||||
* @return updated db statement
|
||||
*/
|
||||
<T> StatementParameters namedParam(T parameters);
|
||||
|
||||
/**
|
||||
* Configure parameters using {@link Object} instance with registered mapper.
|
||||
* The statement must use indexed parameters and configure them by order in the array provided by mapper.
|
||||
*
|
||||
* @param parameters {@link Object} instance containing parameters
|
||||
* @param <T> type of the parameters
|
||||
* @return updated db statement
|
||||
*/
|
||||
<T> StatementParameters indexedParam(T parameters);
|
||||
|
||||
/**
|
||||
* Configure parameters using {@link Object} instance with registered mapper.
|
||||
*
|
||||
* @param parameters {@link Object} instance containing parameters
|
||||
* @param mapper method to create map of statement named parameters mapped to values to be set
|
||||
* @param <T> type of the parameters
|
||||
* @return updated db statement
|
||||
*/
|
||||
default <T> StatementParameters namedParam(T parameters, Function<T, Map<String, ?>> mapper) {
|
||||
return params(mapper.apply(parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure parameters using {@link Object} instance with registered mapper.
|
||||
*
|
||||
* @param parameters {@link Object} instance containing parameters
|
||||
* @param mapper method to create map of statement named parameters mapped to values to be set
|
||||
* @param <T> type of the parameters
|
||||
* @return updated db statement
|
||||
*/
|
||||
default <T> StatementParameters indexedParam(T parameters, Function<T, List<?>> mapper) {
|
||||
return params(mapper.apply(parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add next parameter to the list of ordered parameters (e.g. the ones that use {@code ?} in SQL).
|
||||
*
|
||||
* @param parameter next parameter to set on this statement
|
||||
* @return updated db statement
|
||||
*/
|
||||
StatementParameters addParam(Object parameter);
|
||||
|
||||
/**
|
||||
* Add next parameter to the map of named parameters (e.g. the ones that use {@code :name} in Helidon
|
||||
* JDBC SQL integration).
|
||||
*
|
||||
* @param name name of parameter
|
||||
* @param parameter value of parameter
|
||||
* @return updated db statement
|
||||
*/
|
||||
StatementParameters addParam(String name, Object parameter);
|
||||
|
||||
/**
|
||||
* Return {@code Map} containing all named parameters.
|
||||
*
|
||||
* @return {@code Map} containing all named parameters
|
||||
*/
|
||||
Map<String, Object> namedParams();
|
||||
|
||||
/**
|
||||
* Return {@code List} containing all ordered parameters.
|
||||
*
|
||||
* @return {@code List} containing all ordered parameters
|
||||
*/
|
||||
List<Object> indexedParams();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
/**
|
||||
* Helper classes to use in various implementations.
|
||||
*/
|
||||
package io.helidon.dbclient.common;
|
||||
29
dbclient/common/src/main/java/module-info.java
Normal file
29
dbclient/common/src/main/java/module-info.java
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helidon DB Client Common.
|
||||
*/
|
||||
module io.helidon.dbclient.common {
|
||||
requires java.logging;
|
||||
requires java.sql;
|
||||
|
||||
requires transitive io.helidon.common;
|
||||
requires transitive io.helidon.common.configurable;
|
||||
requires transitive io.helidon.dbclient;
|
||||
|
||||
exports io.helidon.dbclient.common;
|
||||
}
|
||||
60
dbclient/dbclient/pom.xml
Normal file
60
dbclient/dbclient/pom.xml
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
Licensed 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>helidon-dbclient-project</artifactId>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>helidon-dbclient</artifactId>
|
||||
<name>Helidon DB Client</name>
|
||||
<description>Helidon Reactive Database Client API</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.common</groupId>
|
||||
<artifactId>helidon-common-service-loader</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.common</groupId>
|
||||
<artifactId>helidon-common-context</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.common</groupId>
|
||||
<artifactId>helidon-common-mapper</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,340 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.helidon.common.HelidonFeatures;
|
||||
import io.helidon.common.HelidonFlavor;
|
||||
import io.helidon.common.mapper.MapperManager;
|
||||
import io.helidon.common.serviceloader.HelidonServiceLoader;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigValue;
|
||||
import io.helidon.dbclient.spi.DbClientProvider;
|
||||
import io.helidon.dbclient.spi.DbClientProviderBuilder;
|
||||
import io.helidon.dbclient.spi.DbInterceptorProvider;
|
||||
import io.helidon.dbclient.spi.DbMapperProvider;
|
||||
|
||||
/**
|
||||
* Helidon database client.
|
||||
*/
|
||||
public interface DbClient {
|
||||
/**
|
||||
* Execute database statements in transaction.
|
||||
*
|
||||
* @param <T> statement execution result type
|
||||
* @param executor database statement executor, see {@link DbExecute}
|
||||
* @return statement execution result
|
||||
*/
|
||||
<T> CompletionStage<T> inTransaction(Function<DbTransaction, CompletionStage<T>> executor);
|
||||
|
||||
/**
|
||||
* Execute database statement.
|
||||
*
|
||||
* @param <T> statement execution result type
|
||||
* @param executor database statement executor, see {@link DbExecute}
|
||||
* @return statement execution result
|
||||
*/
|
||||
<T extends CompletionStage<?>> T execute(Function<DbExecute, T> executor);
|
||||
|
||||
/**
|
||||
* Pings the database, completes when DB is up and ready, completes exceptionally if not.
|
||||
*
|
||||
* @return stage that completes when the ping finished
|
||||
*/
|
||||
CompletionStage<Void> ping();
|
||||
|
||||
/**
|
||||
* Type of this database provider (such as jdbc:mysql, mongoDB etc.).
|
||||
*
|
||||
* @return name of the database provider
|
||||
*/
|
||||
String dbType();
|
||||
|
||||
/**
|
||||
* Create Helidon database handler builder.
|
||||
*
|
||||
* @param config name of the configuration node with driver configuration
|
||||
* @return database handler builder
|
||||
*/
|
||||
static DbClient create(Config config) {
|
||||
return builder(config).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Helidon database handler builder.
|
||||
* <p>Database driver is loaded as SPI provider which implements {@link io.helidon.dbclient.spi.DbClientProvider} interface.
|
||||
* First provider on the class path is selected.</p>
|
||||
*
|
||||
* @return database handler builder
|
||||
*/
|
||||
static Builder builder() {
|
||||
DbClientProvider theSource = DbClientProviderLoader.first();
|
||||
if (null == theSource) {
|
||||
throw new DbClientException(
|
||||
"No DbSource defined on classpath/module path. An implementation of io.helidon.dbclient.spi.DbSource is required "
|
||||
+ "to access a DB");
|
||||
}
|
||||
|
||||
return builder(theSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Helidon database handler builder.
|
||||
*
|
||||
* @param source database driver
|
||||
* @return database handler builder
|
||||
*/
|
||||
static Builder builder(DbClientProvider source) {
|
||||
return new Builder(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Helidon database handler builder.
|
||||
* <p>Database driver is loaded as SPI provider which implements {@link io.helidon.dbclient.spi.DbClientProvider} interface.
|
||||
* Provider on the class path with matching name is selected.</p>
|
||||
*
|
||||
* @param dbSource SPI provider name
|
||||
* @return database handler builder
|
||||
*/
|
||||
static Builder builder(String dbSource) {
|
||||
return DbClientProviderLoader.get(dbSource)
|
||||
.map(DbClient::builder)
|
||||
.orElseThrow(() -> new DbClientException(
|
||||
"No DbSource defined on classpath/module path for name: "
|
||||
+ dbSource
|
||||
+ ", available names: " + Arrays.toString(DbClientProviderLoader.names())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Helidon database handler builder from configuration.
|
||||
*
|
||||
* @param dbConfig configuration that should contain the key {@code source} that defines the type of this database
|
||||
* and is used to load appropriate {@link io.helidon.dbclient.spi.DbClientProvider} from Java Service loader
|
||||
* @return a builder pre-configured from the provided config
|
||||
*/
|
||||
static Builder builder(Config dbConfig) {
|
||||
return dbConfig.get("source")
|
||||
.asString()
|
||||
// use builder for correct DbSource
|
||||
.map(DbClient::builder)
|
||||
// or use the default one
|
||||
.orElseGet(DbClient::builder)
|
||||
.config(dbConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helidon database handler builder.
|
||||
*/
|
||||
final class Builder implements io.helidon.common.Builder<DbClient> {
|
||||
static {
|
||||
HelidonFeatures.register(HelidonFlavor.SE, "DbClient");
|
||||
}
|
||||
|
||||
private final HelidonServiceLoader.Builder<DbInterceptorProvider> interceptorServices = HelidonServiceLoader.builder(
|
||||
ServiceLoader.load(DbInterceptorProvider.class));
|
||||
|
||||
/**
|
||||
* Provider specific database handler builder instance.
|
||||
*/
|
||||
private final DbClientProviderBuilder<?> theBuilder;
|
||||
private Config config;
|
||||
|
||||
/**
|
||||
* Create an instance of Helidon database handler builder.
|
||||
*
|
||||
* @param dbClientProvider provider specific {@link io.helidon.dbclient.spi.DbClientProvider} instance
|
||||
*/
|
||||
private Builder(DbClientProvider dbClientProvider) {
|
||||
this.theBuilder = dbClientProvider.builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build provider specific database handler.
|
||||
*
|
||||
* @return new database handler instance
|
||||
*/
|
||||
@Override
|
||||
public DbClient build() {
|
||||
// add interceptors from service loader
|
||||
if (null != config) {
|
||||
Config interceptors = config.get("interceptors");
|
||||
List<DbInterceptorProvider> providers = interceptorServices.build().asList();
|
||||
for (DbInterceptorProvider provider : providers) {
|
||||
Config providerConfig = interceptors.get(provider.configKey());
|
||||
if (!providerConfig.exists()) {
|
||||
continue;
|
||||
}
|
||||
// if configured, we want to at least add a global one
|
||||
AtomicBoolean added = new AtomicBoolean(false);
|
||||
Config global = providerConfig.get("global");
|
||||
if (global.exists() && !global.isLeaf()) {
|
||||
// we must iterate through nodes
|
||||
global.asNodeList().ifPresent(configs -> {
|
||||
configs.forEach(globalConfig -> {
|
||||
added.set(true);
|
||||
addInterceptor(provider.create(globalConfig));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Config named = providerConfig.get("named");
|
||||
if (named.exists()) {
|
||||
// we must iterate through nodes
|
||||
named.asNodeList().ifPresent(configs -> {
|
||||
configs.forEach(namedConfig -> {
|
||||
ConfigValue<List<String>> names = namedConfig.get("names").asList(String.class);
|
||||
names.ifPresent(nameList -> {
|
||||
added.set(true);
|
||||
addInterceptor(provider.create(namedConfig), nameList.toArray(new String[0]));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
Config typed = providerConfig.get("typed");
|
||||
if (typed.exists()) {
|
||||
typed.asNodeList().ifPresent(configs -> {
|
||||
configs.forEach(typedConfig -> {
|
||||
ConfigValue<List<String>> types = typedConfig.get("types").asList(String.class);
|
||||
types.ifPresent(typeList -> {
|
||||
DbStatementType[] typeArray = typeList.stream()
|
||||
.map(DbStatementType::valueOf)
|
||||
.toArray(DbStatementType[]::new);
|
||||
|
||||
added.set(true);
|
||||
addInterceptor(provider.create(typedConfig), typeArray);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
if (!added.get()) {
|
||||
if (global.exists()) {
|
||||
addInterceptor(provider.create(global));
|
||||
} else {
|
||||
addInterceptor(provider.create(providerConfig));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return theBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an interceptor provider.
|
||||
* The provider is only used when configuration is used ({@link #config(io.helidon.config.Config)}.
|
||||
*
|
||||
* @param provider provider to add to the list of loaded providers
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public Builder addInterceptorProvider(DbInterceptorProvider provider) {
|
||||
this.interceptorServices.addService(provider);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a global interceptor.
|
||||
*
|
||||
* A global interceptor is applied to each statement.
|
||||
* @param interceptor interceptor to apply
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public Builder addInterceptor(DbInterceptor interceptor) {
|
||||
theBuilder.addInterceptor(interceptor);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an interceptor to specific named statements.
|
||||
*
|
||||
* @param interceptor interceptor to apply
|
||||
* @param statementNames names of statements to apply it on
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public Builder addInterceptor(DbInterceptor interceptor, String... statementNames) {
|
||||
theBuilder.addInterceptor(interceptor, statementNames);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an interceptor to specific statement types.
|
||||
*
|
||||
* @param interceptor interceptor to apply
|
||||
* @param dbStatementTypes types of statements to apply it on
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public Builder addInterceptor(DbInterceptor interceptor, DbStatementType... dbStatementTypes) {
|
||||
theBuilder.addInterceptor(interceptor, dbStatementTypes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use database connection configuration from configuration file.
|
||||
*
|
||||
* @param config {@link io.helidon.config.Config} instance with database connection attributes
|
||||
* @return database provider builder
|
||||
*/
|
||||
public Builder config(Config config) {
|
||||
theBuilder.config(config);
|
||||
|
||||
this.config = config;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Statements to use either from configuration
|
||||
* or manually configured.
|
||||
*
|
||||
* @param statements Statements to use
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public Builder statements(DbStatements statements) {
|
||||
theBuilder.statements(statements);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Database schema mappers provider.
|
||||
* Mappers associated with types in this provider will override existing types associations loaded
|
||||
* as {@link io.helidon.dbclient.spi.DbMapperProvider} Java services.
|
||||
*
|
||||
* @param provider database schema mappers provider to use
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public Builder mapperProvider(DbMapperProvider provider) {
|
||||
theBuilder.addMapperProvider(provider);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapper manager for generic mapping, such as mapping of parameters to expected types.
|
||||
*
|
||||
* @param manager mapper manager
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public Builder mapperManager(MapperManager manager) {
|
||||
theBuilder.mapperManager(manager);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
/**
|
||||
* A {@link RuntimeException} used by Helidon DB component.
|
||||
*/
|
||||
public class DbClientException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Create a new exception for a message.
|
||||
* @param message descriptive message
|
||||
*/
|
||||
public DbClientException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new exception for a message and a cause.
|
||||
*
|
||||
* @param message descriptive message
|
||||
* @param cause original throwable causing this exception
|
||||
*/
|
||||
public DbClientException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
import io.helidon.common.serviceloader.HelidonServiceLoader;
|
||||
import io.helidon.dbclient.spi.DbClientProvider;
|
||||
|
||||
/**
|
||||
* Loads database client providers from Java Service loader.
|
||||
*/
|
||||
final class DbClientProviderLoader {
|
||||
|
||||
private static final Map<String, DbClientProvider> DB_SOURCES = new HashMap<>();
|
||||
private static final String[] NAMES;
|
||||
private static final DbClientProvider FIRST;
|
||||
|
||||
static {
|
||||
HelidonServiceLoader<DbClientProvider> serviceLoader = HelidonServiceLoader
|
||||
.builder(ServiceLoader.load(DbClientProvider.class))
|
||||
.build();
|
||||
|
||||
List<DbClientProvider> sources = serviceLoader.asList();
|
||||
|
||||
DbClientProvider first = null;
|
||||
|
||||
if (!sources.isEmpty()) {
|
||||
first = sources.get(0);
|
||||
}
|
||||
|
||||
FIRST = first;
|
||||
sources.forEach(dbProvider -> DB_SOURCES.put(dbProvider.name(), dbProvider));
|
||||
NAMES = sources.stream()
|
||||
.map(DbClientProvider::name)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
private DbClientProviderLoader() {
|
||||
}
|
||||
|
||||
static DbClientProvider first() {
|
||||
return FIRST;
|
||||
}
|
||||
|
||||
static Optional<DbClientProvider> get(String name) {
|
||||
return Optional.ofNullable(DB_SOURCES.get(name));
|
||||
}
|
||||
|
||||
static String[] names() {
|
||||
return NAMES;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import io.helidon.common.GenericType;
|
||||
import io.helidon.common.mapper.MapperException;
|
||||
|
||||
/**
|
||||
* Column data and metadata.
|
||||
*/
|
||||
public interface DbColumn {
|
||||
/**
|
||||
* Typed value of this column.
|
||||
* This method can return a correct result only if the type is the same as {@link #javaType()} or there is a
|
||||
* {@link io.helidon.common.mapper.Mapper} registered that can map it.
|
||||
*
|
||||
* @param type class of the type that should be returned (must be supported by the underlying data type)
|
||||
* @param <T> type of the returned value
|
||||
* @return value of this column correctly typed
|
||||
* @throws MapperException in case the type is not the underlying {@link #javaType()} and
|
||||
* there is no mapper registered for it
|
||||
*/
|
||||
<T> T as(Class<T> type) throws MapperException;
|
||||
|
||||
/**
|
||||
* Value of this column as a generic type.
|
||||
* This method can return a correct result only if the type represents a class, or if there is a
|
||||
* {@link io.helidon.common.mapper.Mapper} registered that can map underlying {@link #javaType()} to the type requested.
|
||||
*
|
||||
* @param type requested type
|
||||
* @param <T> type of the returned value
|
||||
* @return value mapped to the expected type if possible
|
||||
* @throws MapperException in case the mapping cannot be done
|
||||
*/
|
||||
<T> T as(GenericType<T> type) throws MapperException;
|
||||
|
||||
/**
|
||||
* Untyped value of this column, returns java type as provided by the underlying database driver.
|
||||
*
|
||||
* @return value of this column
|
||||
*/
|
||||
default Object value() {
|
||||
return as(javaType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of the column as would be returned by the underlying database driver.
|
||||
*
|
||||
* @return class of the type
|
||||
* @see #dbType()
|
||||
*/
|
||||
Class<?> javaType();
|
||||
|
||||
/**
|
||||
* Type of the column in the language of the database.
|
||||
* <p>
|
||||
* Example for SQL - if a column is declared as {@code VARCHAR(256)} in the database,
|
||||
* this method would return {@code VARCHAR} and method {@link #javaType()} would return {@link String}.
|
||||
*
|
||||
* @return column type as the database understands it
|
||||
*/
|
||||
String dbType();
|
||||
|
||||
/**
|
||||
* Column name.
|
||||
*
|
||||
* @return name of this column
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* Precision of this column.
|
||||
* <p>
|
||||
* Precision depends on data type:
|
||||
* <ul>
|
||||
* <li>Numeric: The maximal number of digits of the number</li>
|
||||
* <li>String/Character: The maximal length</li>
|
||||
* <li>Binary: The maximal number of bytes</li>
|
||||
* <li>Other: Implementation specific</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return precision of this column or {@code empty} if precision is not available
|
||||
*/
|
||||
default Optional<Integer> precision() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale of this column.
|
||||
* <p>
|
||||
* Scale is the number of digits in a decimal number to the right of the decimal separator.
|
||||
*
|
||||
* @return scale of this column or {@code empty} if scale is not available
|
||||
*/
|
||||
default Optional<Integer> scale() {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
import io.helidon.common.reactive.OptionalCompletionStage;
|
||||
|
||||
/**
|
||||
* Database executor.
|
||||
* <p>The database executor provides methods to create {@link DbStatement} instances for different types
|
||||
* of database statements.</p>
|
||||
* <p>The recommended approach is to use named statements, as that allows better metrics, tracing, logging etc.
|
||||
* In case an unnamed statement is used, a name must be generated
|
||||
* <p>There are five methods for each {@link DbStatementType}, example for query (the implementation
|
||||
* detail is for the default implementation, providers may differ):
|
||||
* <ol>
|
||||
* <li>{@code DbStatement} {@link #createNamedQuery(String, String)} - full control over the name and content of the
|
||||
* statement</li>
|
||||
* <li>{@code DbStatement} {@link #createNamedQuery(String)} - use statement text from configuration</li>
|
||||
* <li>{@code DbStatement} {@link #createQuery(String)} - use the provided statement, name is generated </li>
|
||||
* <li>{@code DbRowResult} {@link #namedQuery(String, Object...)} - shortcut method to a named query with a list of
|
||||
* parameters (or with no parameters at all)</li>
|
||||
* <li>{@code DbRowResult} {@link #query(String, Object...)} - shortcut method to unnamed query with a list of parameters
|
||||
* (or with no parameters at all)</li>
|
||||
* </ol>
|
||||
* The first three methods return a statement that can have parameters configured (and other details modified).
|
||||
* The last two methods directly execute the statement and provide appropriate response for future processing.
|
||||
* All the methods are non-blocking.
|
||||
*/
|
||||
public interface DbExecute {
|
||||
/*
|
||||
* QUERY
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a database query using a named statement passed as argument.
|
||||
*
|
||||
* @param statementName the name of the statement
|
||||
* @param statement the query statement
|
||||
* @return database statement that can process query returning multiple rows
|
||||
*/
|
||||
DbStatementQuery createNamedQuery(String statementName, String statement);
|
||||
|
||||
/**
|
||||
* Create a database query using a statement defined in the configuration file.
|
||||
*
|
||||
* @param statementName the name of the configuration node with statement
|
||||
* @return database statement that can process query returning multiple rows
|
||||
*/
|
||||
DbStatementQuery createNamedQuery(String statementName);
|
||||
|
||||
/**
|
||||
* Create a database query using a statement passed as an argument.
|
||||
*
|
||||
* @param statement the query statement to be executed
|
||||
* @return database statement that can process the query returning multiple rows
|
||||
*/
|
||||
DbStatementQuery createQuery(String statement);
|
||||
|
||||
/**
|
||||
* Create and execute a database query using a statement defined in the configuration file.
|
||||
*
|
||||
* @param statementName the name of the configuration node with statement
|
||||
* @param parameters query parameters to set
|
||||
* @return database query execution result which can contain multiple rows
|
||||
*/
|
||||
default CompletionStage<DbRows<DbRow>> namedQuery(String statementName, Object... parameters) {
|
||||
return createNamedQuery(statementName).params(parameters).execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and execute a database query using a statement passed as an argument.
|
||||
*
|
||||
* @param statement the query statement to be executed
|
||||
* @param parameters query parameters to set
|
||||
* @return database query execution result which can contain multiple rows
|
||||
*/
|
||||
default CompletionStage<DbRows<DbRow>> query(String statement, Object... parameters) {
|
||||
return createQuery(statement).params(parameters).execute();
|
||||
}
|
||||
|
||||
/*
|
||||
* GET
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a database query returning a single row using a named statement passed as an argument.
|
||||
*
|
||||
* @param statementName the name of the statement
|
||||
* @param statement the statement text
|
||||
* @return database statement that can process query returning a single row
|
||||
*/
|
||||
DbStatementGet createNamedGet(String statementName, String statement);
|
||||
|
||||
/**
|
||||
* Create a database query returning a single row using a statement defined in the configuration file.
|
||||
*
|
||||
* @param statementName the name of the configuration node with statement
|
||||
* @return database statement that can process query returning a single row
|
||||
*/
|
||||
DbStatementGet createNamedGet(String statementName);
|
||||
|
||||
/**
|
||||
* Create a database query returning a single row using a statement passed as an argument.
|
||||
*
|
||||
* @param statement the query statement to be executed
|
||||
* @return database statement that can process query returning a single row
|
||||
*/
|
||||
DbStatementGet createGet(String statement);
|
||||
|
||||
/**
|
||||
* Create and execute a database query using a statement defined in the configuration file.
|
||||
*
|
||||
* @param statementName the name of the configuration node with statement
|
||||
* @param parameters query parameters to set
|
||||
* @return database query execution result which can contain single row
|
||||
*/
|
||||
default OptionalCompletionStage<DbRow> namedGet(String statementName, Object... parameters) {
|
||||
return OptionalCompletionStage.create(createNamedGet(statementName).params(parameters).execute());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and execute a database query using a statement passed as an argument.
|
||||
*
|
||||
* @param statement the query statement to be executed
|
||||
* @param parameters query parameters to set
|
||||
* @return database query execution result which can contain single row
|
||||
*/
|
||||
default CompletionStage<Optional<DbRow>> get(String statement, Object... parameters) {
|
||||
return createGet(statement).params(parameters).execute();
|
||||
}
|
||||
|
||||
/*
|
||||
* INSERT
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create an insert statement using a named statement passed as an argument.
|
||||
*
|
||||
* @param statementName the name of the statement
|
||||
* @param statement the statement text
|
||||
* @return database statement that can insert data
|
||||
*/
|
||||
default DbStatementDml createNamedInsert(String statementName, String statement) {
|
||||
return createNamedDmlStatement(statementName, statement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an insert statement using a named statement.
|
||||
*
|
||||
* @param statementName the name of the statement
|
||||
* @return database statement that can insert data
|
||||
*/
|
||||
DbStatementDml createNamedInsert(String statementName);
|
||||
|
||||
/**
|
||||
* Create an insert statement using a statement text.
|
||||
*
|
||||
* @param statement the statement text
|
||||
* @return database statement that can insert data
|
||||
*/
|
||||
DbStatementDml createInsert(String statement);
|
||||
|
||||
/**
|
||||
* Create and execute insert statement using a statement defined in the configuration file.
|
||||
*
|
||||
* @param statementName the name of the configuration node with statement
|
||||
* @param parameters query parameters to set
|
||||
* @return number of rows inserted into the database
|
||||
*/
|
||||
default CompletionStage<Long> namedInsert(String statementName, Object... parameters) {
|
||||
return createNamedInsert(statementName).params(parameters).execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and execute insert statement using a statement passed as an argument.
|
||||
*
|
||||
* @param statement the insert statement to be executed
|
||||
* @param parameters query parameters to set
|
||||
* @return number of rows inserted into the database
|
||||
*/
|
||||
default CompletionStage<Long> insert(String statement, Object... parameters) {
|
||||
return createInsert(statement).params(parameters).execute();
|
||||
}
|
||||
|
||||
/*
|
||||
* UPDATE
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create an update statement using a named statement passed as an argument.
|
||||
*
|
||||
* @param statementName the name of the statement
|
||||
* @param statement the statement text
|
||||
* @return database statement that can update data
|
||||
*/
|
||||
default DbStatementDml createNamedUpdate(String statementName, String statement) {
|
||||
return createNamedDmlStatement(statementName, statement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an update statement using a named statement.
|
||||
*
|
||||
* @param statementName the name of the statement
|
||||
* @return database statement that can update data
|
||||
*/
|
||||
DbStatementDml createNamedUpdate(String statementName);
|
||||
|
||||
/**
|
||||
* Create an update statement using a statement text.
|
||||
*
|
||||
* @param statement the statement text
|
||||
* @return database statement that can update data
|
||||
*/
|
||||
DbStatementDml createUpdate(String statement);
|
||||
|
||||
/**
|
||||
* Create and execute update statement using a statement defined in the configuration file.
|
||||
*
|
||||
* @param statementName the name of the configuration node with statement
|
||||
* @param parameters query parameters to set
|
||||
* @return number of rows updateed into the database
|
||||
*/
|
||||
default CompletionStage<Long> namedUpdate(String statementName, Object... parameters) {
|
||||
return createNamedUpdate(statementName).params(parameters).execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and execute update statement using a statement passed as an argument.
|
||||
*
|
||||
* @param statement the update statement to be executed
|
||||
* @param parameters query parameters to set
|
||||
* @return number of rows updateed into the database
|
||||
*/
|
||||
default CompletionStage<Long> update(String statement, Object... parameters) {
|
||||
return createUpdate(statement).params(parameters).execute();
|
||||
}
|
||||
|
||||
/*
|
||||
* DELETE
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a delete statement using a named statement passed as an argument.
|
||||
*
|
||||
* @param statementName the name of the statement
|
||||
* @param statement the statement text
|
||||
* @return database statement that can delete data
|
||||
*/
|
||||
default DbStatementDml createNamedDelete(String statementName, String statement) {
|
||||
return createNamedDmlStatement(statementName, statement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create andelete statement using a named statement.
|
||||
*
|
||||
* @param statementName the name of the statement
|
||||
* @return database statement that can delete data
|
||||
*/
|
||||
DbStatementDml createNamedDelete(String statementName);
|
||||
|
||||
/**
|
||||
* Create a delete statement using a statement text.
|
||||
*
|
||||
* @param statement the statement text
|
||||
* @return database statement that can delete data
|
||||
*/
|
||||
DbStatementDml createDelete(String statement);
|
||||
|
||||
/**
|
||||
* Create and execute delete statement using a statement defined in the configuration file.
|
||||
*
|
||||
* @param statementName the name of the configuration node with statement
|
||||
* @param parameters query parameters to set
|
||||
* @return number of rows deleted from the database
|
||||
*/
|
||||
default CompletionStage<Long> namedDelete(String statementName, Object... parameters) {
|
||||
return createNamedDelete(statementName).params(parameters).execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and execute delete statement using a statement passed as an argument.
|
||||
*
|
||||
* @param statement the delete statement to be executed
|
||||
* @param parameters query parameters to set
|
||||
* @return number of rows deleted from the database
|
||||
*/
|
||||
default CompletionStage<Long> delete(String statement, Object... parameters) {
|
||||
return createDelete(statement).params(parameters).execute();
|
||||
}
|
||||
|
||||
/*
|
||||
* DML
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a data modification statement using a named statement passed as an argument.
|
||||
*
|
||||
* @param statementName the name of the statement
|
||||
* @param statement the statement text
|
||||
* @return data modification statement
|
||||
*/
|
||||
DbStatementDml createNamedDmlStatement(String statementName, String statement);
|
||||
|
||||
/**
|
||||
* Create a data modification statement using a statement defined in the configuration file.
|
||||
*
|
||||
* @param statementName the name of the configuration node with statement
|
||||
* @return data modification statement
|
||||
*/
|
||||
DbStatementDml createNamedDmlStatement(String statementName);
|
||||
|
||||
/**
|
||||
* Create a data modification statement using a statement passed as an argument.
|
||||
*
|
||||
* @param statement the data modification statement to be executed
|
||||
* @return data modification statement
|
||||
*/
|
||||
DbStatementDml createDmlStatement(String statement);
|
||||
|
||||
/**
|
||||
* Create and execute a data modification statement using a statement defined in the configuration file.
|
||||
*
|
||||
* @param statementName the name of the configuration node with statement
|
||||
* @param parameters query parameters to set
|
||||
* @return number of rows modified
|
||||
*/
|
||||
default CompletionStage<Long> namedDml(String statementName, Object... parameters) {
|
||||
return createNamedDmlStatement(statementName).params(parameters).execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and execute data modification statement using a statement passed as an argument.
|
||||
*
|
||||
* @param statement the delete statement to be executed
|
||||
* @param parameters query parameters to set
|
||||
* @return number of rows modified
|
||||
*/
|
||||
default CompletionStage<Long> dml(String statement, Object... parameters) {
|
||||
return createDmlStatement(statement).params(parameters).execute();
|
||||
}
|
||||
|
||||
/*
|
||||
* UNKNOWN
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a generic database statement using a named statement passed as an argument.
|
||||
*
|
||||
* @param statementName the name of the statement
|
||||
* @param statement the statement text
|
||||
* @return generic database statement that can return any result
|
||||
*/
|
||||
DbStatementGeneric createNamedStatement(String statementName, String statement);
|
||||
|
||||
/**
|
||||
* Create a generic database statement using a statement defined in the configuration file.
|
||||
*
|
||||
* @param statementName the name of the configuration node with statement
|
||||
* @return generic database statement that can return any result
|
||||
*/
|
||||
DbStatementGeneric createNamedStatement(String statementName);
|
||||
|
||||
/**
|
||||
* Create a generic database statement using a statement passed as an argument.
|
||||
*
|
||||
* @param statement the statement to be executed
|
||||
* @return generic database statement that can return any result
|
||||
*/
|
||||
DbStatementGeneric createStatement(String statement);
|
||||
|
||||
/**
|
||||
* Create and execute common statement using a statement defined in the configuration file.
|
||||
*
|
||||
* @param statementName the name of the configuration node with statement
|
||||
* @param parameters query parameters to set
|
||||
* @return generic statement execution result
|
||||
*/
|
||||
default CompletionStage<DbResult> namedStatement(String statementName, Object... parameters) {
|
||||
return createNamedStatement(statementName).params(parameters).execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and execute common statement using a statement passed as an argument.
|
||||
*
|
||||
* @param statement the statement to be executed
|
||||
* @param parameters query parameters to set
|
||||
* @return generic statement execution result
|
||||
*/
|
||||
default CompletionStage<DbResult> statement(String statement, Object... parameters) {
|
||||
return createStatement(statement).params(parameters).execute();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
/**
|
||||
* Interceptor to handle work around a database statement.
|
||||
* Example of such interceptors: tracing, metrics.
|
||||
* <p>
|
||||
* Interceptors can be defined as global interceptors, interceptors for a type of a statement and interceptors for a named
|
||||
* statement.
|
||||
* These are executed in the following order:
|
||||
* <ol>
|
||||
* <li>Named interceptors - if there are any interceptors configured for a specific statement, they are executed first</li>
|
||||
* <li>Type interceptors - if there are any interceptors configured for a type of statement, they are executed next</li>
|
||||
* <li>Global interceptors - if there are any interceptors configured globally, they are executed last</li>
|
||||
* </ol>
|
||||
* Order of interceptors within a group is based on the order they are registered in a builder, or by their priority when
|
||||
* loaded from a Java Service loader
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface DbInterceptor {
|
||||
/**
|
||||
* Statement execution to be intercepted.
|
||||
* This method is called before the statement execution starts.
|
||||
*
|
||||
* @param context Context to access data needed to process an interceptor
|
||||
* @return completion stage that completes when this interceptor is finished
|
||||
*/
|
||||
CompletionStage<DbInterceptorContext> statement(DbInterceptorContext context);
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
import io.helidon.common.context.Context;
|
||||
|
||||
/**
|
||||
* Interceptor context to get (and possibly manipulate) database operations.
|
||||
* <p>
|
||||
* This is a mutable object - acts as a builder during the invocation of {@link io.helidon.dbclient.DbInterceptor}.
|
||||
* The interceptors are executed sequentially, so there is no need for synchronization.
|
||||
*/
|
||||
public interface DbInterceptorContext {
|
||||
/**
|
||||
* Create a new interceptor context for a database provider.
|
||||
*
|
||||
* @param dbType a short name of the db type (such as jdbc:mysql)
|
||||
* @return a new interceptor context ready to be configured
|
||||
*/
|
||||
static DbInterceptorContext create(String dbType) {
|
||||
return new DbInterceptorContextImpl(dbType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of this database (usually the same string used by the {@link io.helidon.dbclient.spi.DbClientProvider#name()}).
|
||||
*
|
||||
* @return type of database
|
||||
*/
|
||||
String dbType();
|
||||
|
||||
/**
|
||||
* Context with parameters passed from the caller, such as {@code SpanContext} for tracing.
|
||||
*
|
||||
* @return context associated with this request
|
||||
*/
|
||||
Context context();
|
||||
|
||||
/**
|
||||
* Name of a statement to be executed.
|
||||
* Ad hoc statements have names generated.
|
||||
*
|
||||
* @return name of the statement
|
||||
*/
|
||||
String statementName();
|
||||
|
||||
/**
|
||||
* Text of the statement to be executed.
|
||||
*
|
||||
* @return statement text
|
||||
*/
|
||||
String statement();
|
||||
|
||||
/**
|
||||
* A stage that is completed once the statement finishes execution.
|
||||
*
|
||||
* @return statement future
|
||||
*/
|
||||
CompletionStage<Void> statementFuture();
|
||||
|
||||
/**
|
||||
* A stage that is completed once the results were fully read. The number returns either the number of modified
|
||||
* records or the number of records actually read.
|
||||
*
|
||||
* @return stage that completes once all query results were processed.
|
||||
*/
|
||||
CompletionStage<Long> resultFuture();
|
||||
|
||||
/**
|
||||
* Indexed parameters (if used).
|
||||
*
|
||||
* @return indexed parameters (empty if this statement parameters are not indexed)
|
||||
*/
|
||||
Optional<List<Object>> indexedParameters();
|
||||
|
||||
/**
|
||||
* Named parameters (if used).
|
||||
*
|
||||
* @return named parameters (empty if this statement parameters are not named)
|
||||
*/
|
||||
Optional<Map<String, Object>> namedParameters();
|
||||
|
||||
/**
|
||||
* Whether this is a statement with indexed parameters.
|
||||
*
|
||||
* @return Whether this statement has indexed parameters ({@code true}) or named parameters {@code false}.
|
||||
*/
|
||||
boolean isIndexed();
|
||||
|
||||
/**
|
||||
* Whether this is a statement with named parameters.
|
||||
*
|
||||
* @return Whether this statement has named parameters ({@code true}) or indexed parameters {@code false}.
|
||||
*/
|
||||
boolean isNamed();
|
||||
|
||||
/**
|
||||
* Type of the statement being executed.
|
||||
* @return statement type
|
||||
*/
|
||||
DbStatementType statementType();
|
||||
|
||||
/**
|
||||
* Set a new context to be used by other interceptors and when executing the statement.
|
||||
*
|
||||
* @param context context to use
|
||||
* @return updated interceptor context
|
||||
*/
|
||||
DbInterceptorContext context(Context context);
|
||||
|
||||
/**
|
||||
* Set a new statement name to be used.
|
||||
*
|
||||
* @param newName statement name to use
|
||||
* @return updated interceptor context
|
||||
*/
|
||||
DbInterceptorContext statementName(String newName);
|
||||
|
||||
/**
|
||||
* Set a new future to mark completion of the statement.
|
||||
*
|
||||
* @param statementFuture future
|
||||
* @return updated interceptor context
|
||||
*/
|
||||
DbInterceptorContext statementFuture(CompletionStage<Void> statementFuture);
|
||||
|
||||
/**
|
||||
* Set a new future to mark completion of the result (e.g. query or number of modified records).
|
||||
*
|
||||
* @param queryFuture future
|
||||
* @return updated interceptor context
|
||||
*/
|
||||
DbInterceptorContext resultFuture(CompletionStage<Long> queryFuture);
|
||||
|
||||
/**
|
||||
* Set a new statement with indexed parameters to be used.
|
||||
*
|
||||
* @param statement statement text
|
||||
* @param indexedParams indexed parameters
|
||||
* @return updated interceptor context
|
||||
*/
|
||||
DbInterceptorContext statement(String statement, List<Object> indexedParams);
|
||||
|
||||
/**
|
||||
* Set a new statement with named parameters to be used.
|
||||
*
|
||||
* @param statement statement text
|
||||
* @param namedParams named parameters
|
||||
* @return updated interceptor context
|
||||
*/
|
||||
DbInterceptorContext statement(String statement, Map<String, Object> namedParams);
|
||||
|
||||
/**
|
||||
* Set new indexed parameters to be used.
|
||||
*
|
||||
* @param indexedParameters parameters
|
||||
* @return updated interceptor context
|
||||
* @throws IllegalArgumentException in case the statement is using named parameters
|
||||
*/
|
||||
DbInterceptorContext parameters(List<Object> indexedParameters);
|
||||
|
||||
/**
|
||||
* Set new named parameters to be used.
|
||||
*
|
||||
* @param namedParameters parameters
|
||||
* @return updated interceptor context
|
||||
* @throws IllegalArgumentException in case the statement is using indexed parameters
|
||||
*/
|
||||
DbInterceptorContext parameters(Map<String, Object> namedParameters);
|
||||
|
||||
/**
|
||||
* Set the type of the statement.
|
||||
*
|
||||
* @param type statement type
|
||||
* @return updated interceptor context
|
||||
*/
|
||||
DbInterceptorContext statementType(DbStatementType type);
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
import io.helidon.common.context.Context;
|
||||
|
||||
/**
|
||||
* Interceptor is a mutable object that is sent to {@link io.helidon.dbclient.DbInterceptor}.
|
||||
*/
|
||||
class DbInterceptorContextImpl implements DbInterceptorContext {
|
||||
private final String dbType;
|
||||
private DbStatementType dbStatementType = DbStatementType.UNKNOWN;
|
||||
private Context context;
|
||||
private String statementName;
|
||||
private String statement;
|
||||
private CompletionStage<Void> statementFuture;
|
||||
private CompletionStage<Long> queryFuture;
|
||||
private List<Object> indexedParams;
|
||||
private Map<String, Object> namedParams;
|
||||
private boolean indexed;
|
||||
|
||||
DbInterceptorContextImpl(String dbType) {
|
||||
this.dbType = dbType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String dbType() {
|
||||
return dbType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context context() {
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String statementName() {
|
||||
return statementName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String statement() {
|
||||
return statement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> statementFuture() {
|
||||
return statementFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<List<Object>> indexedParameters() {
|
||||
if (indexed) {
|
||||
return Optional.of(indexedParams);
|
||||
}
|
||||
throw new IllegalStateException("Indexed parameters are not available for statement with named parameters");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Map<String, Object>> namedParameters() {
|
||||
if (indexed) {
|
||||
throw new IllegalStateException("Named parameters are not available for statement with indexed parameters");
|
||||
}
|
||||
return Optional.of(namedParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIndexed() {
|
||||
return indexed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNamed() {
|
||||
return !indexed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbInterceptorContext context(Context context) {
|
||||
this.context = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbInterceptorContext statementName(String newName) {
|
||||
this.statementName = newName;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbInterceptorContext statementFuture(CompletionStage<Void> statementFuture) {
|
||||
this.statementFuture = statementFuture;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Long> resultFuture() {
|
||||
return queryFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbInterceptorContext resultFuture(CompletionStage<Long> resultFuture) {
|
||||
this.queryFuture = resultFuture;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbInterceptorContext statement(String statement, List<Object> indexedParams) {
|
||||
this.statement = statement;
|
||||
this.indexedParams = indexedParams;
|
||||
this.indexed = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbInterceptorContext statement(String statement, Map<String, Object> namedParams) {
|
||||
this.statement = statement;
|
||||
this.namedParams = namedParams;
|
||||
this.indexed = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbInterceptorContext parameters(List<Object> indexedParameters) {
|
||||
if (indexed) {
|
||||
this.indexedParams = indexedParameters;
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot configure indexed parameters for a statement that expects named "
|
||||
+ "parameters");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbInterceptorContext parameters(Map<String, Object> namedParameters) {
|
||||
if (indexed) {
|
||||
throw new IllegalStateException("Cannot configure named parameters for a statement that expects indexed "
|
||||
+ "parameters");
|
||||
}
|
||||
|
||||
this.namedParams = namedParameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementType statementType() {
|
||||
return dbStatementType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbInterceptorContext statementType(DbStatementType type) {
|
||||
this.dbStatementType = type;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A mapper to map database objects to/from a specific type.
|
||||
* <p>
|
||||
* Mappers can be either provided through {@link io.helidon.dbclient.spi.DbClientProvider} or registered directly
|
||||
* with the {@link io.helidon.dbclient.spi.DbClientProviderBuilder#addMapper(DbMapper, Class)}.
|
||||
*
|
||||
* @param <T> target mapping type
|
||||
*/
|
||||
public interface DbMapper<T> {
|
||||
/**
|
||||
* Read database row and convert it to target type instance.
|
||||
*
|
||||
* @param row source database row
|
||||
* @return target type instance containing database row
|
||||
*/
|
||||
T read(DbRow row);
|
||||
|
||||
/**
|
||||
* Convert target type instance to a statement named parameters map.
|
||||
*
|
||||
* @param value mapping type instance containing values to be set into statement
|
||||
* @return map of statement named parameters mapped to values to be set
|
||||
* @see io.helidon.dbclient.DbStatement#namedParam(Object)
|
||||
*/
|
||||
Map<String, ?> toNamedParameters(T value);
|
||||
|
||||
/**
|
||||
* Convert target type instance to a statement indexed parameters list.
|
||||
* <p>
|
||||
* Using indexed parameters with typed values is probably not going to work nicely, unless
|
||||
* the order is specified and the number of parameters is always related the provided value.
|
||||
* There are cases where this is useful though - e.g. for types that represent an iterable collection.
|
||||
*
|
||||
* @param value mapping type instance containing values to be set into statement
|
||||
* @return map of statement named parameters mapped to values to be set
|
||||
* @see io.helidon.dbclient.DbStatement#indexedParam(Object)
|
||||
*/
|
||||
List<?> toIndexedParameters(T value);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
import io.helidon.common.GenericType;
|
||||
import io.helidon.common.mapper.MapperException;
|
||||
import io.helidon.common.serviceloader.HelidonServiceLoader;
|
||||
import io.helidon.dbclient.spi.DbMapperProvider;
|
||||
|
||||
/**
|
||||
* Mapper manager of all configured {@link io.helidon.dbclient.DbMapper mappers}.
|
||||
*/
|
||||
public interface DbMapperManager {
|
||||
/**
|
||||
* Generic type for the {@link io.helidon.dbclient.DbRow} class.
|
||||
*/
|
||||
GenericType<Object> TYPE_DB_ROW = GenericType.create(DbRow.class);
|
||||
/**
|
||||
* Generic type for the {@link Map} of String to value pairs for named parameters.
|
||||
*/
|
||||
GenericType<Map<String, ?>> TYPE_NAMED_PARAMS = new GenericType<Map<String, ?>>() { };
|
||||
/**
|
||||
* Generic type for the {@link List} of indexed parameters.
|
||||
*/
|
||||
GenericType<List<?>> TYPE_INDEXED_PARAMS = new GenericType<List<?>>() { };
|
||||
|
||||
/**
|
||||
* Create a fluent API builder to configure the mapper manager.
|
||||
*
|
||||
* @return a new builder instance
|
||||
*/
|
||||
static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new mapper manager from Java Service loader only.
|
||||
*
|
||||
* @return mapper manager
|
||||
*/
|
||||
static DbMapperManager create() {
|
||||
return DbMapperManager.builder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new mapper manager from customized {@link io.helidon.common.serviceloader.HelidonServiceLoader}.
|
||||
*
|
||||
* @param serviceLoader service loader to use to read all {@link io.helidon.dbclient.spi.DbMapperProvider}
|
||||
* @return mapper manager
|
||||
*/
|
||||
static DbMapperManager create(HelidonServiceLoader<DbMapperProvider> serviceLoader) {
|
||||
return DbMapperManager.builder()
|
||||
.serviceLoader(serviceLoader)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read database row into a typed value.
|
||||
*
|
||||
* @param row row from a database
|
||||
* @param expectedType class of the response
|
||||
* @param <T> type of the response
|
||||
* @return instance with data from the row
|
||||
* @throws MapperException in case the mapper was not found
|
||||
* @see io.helidon.dbclient.DbRow#as(Class)
|
||||
*/
|
||||
<T> T read(DbRow row, Class<T> expectedType) throws MapperException;
|
||||
|
||||
/**
|
||||
* Read database row into a typed value.
|
||||
*
|
||||
* @param row row from a database
|
||||
* @param expectedType generic type of the response
|
||||
* @param <T> type of the response
|
||||
* @return instance with data from the row
|
||||
* @throws MapperException in case the mapper was not found
|
||||
* @see io.helidon.dbclient.DbRow#as(io.helidon.common.GenericType)
|
||||
*/
|
||||
<T> T read(DbRow row, GenericType<T> expectedType) throws MapperException;
|
||||
|
||||
/**
|
||||
* Read object into a map of named parameters.
|
||||
*
|
||||
* @param value the typed value
|
||||
* @param valueClass type of the value object
|
||||
* @param <T> type of value
|
||||
* @return map with the named parameters
|
||||
* @see io.helidon.dbclient.DbStatement#namedParam(Object)
|
||||
*/
|
||||
<T> Map<String, ?> toNamedParameters(T value, Class<T> valueClass);
|
||||
|
||||
/**
|
||||
* Read object into a list of indexed parameters.
|
||||
*
|
||||
* @param value the typed value
|
||||
* @param valueClass type of the value object
|
||||
* @param <T> type of value
|
||||
* @return list with indexed parameters (in the order expected by statements using this object)
|
||||
* @see io.helidon.dbclient.DbStatement#indexedParam(Object)
|
||||
*/
|
||||
<T> List<?> toIndexedParameters(T value, Class<T> valueClass);
|
||||
|
||||
/**
|
||||
* Fluent API builder for {@link io.helidon.dbclient.DbMapperManager}.
|
||||
*/
|
||||
final class Builder implements io.helidon.common.Builder<DbMapperManager> {
|
||||
|
||||
private final HelidonServiceLoader.Builder<DbMapperProvider> providers = HelidonServiceLoader
|
||||
.builder(ServiceLoader.load(DbMapperProvider.class));
|
||||
|
||||
private HelidonServiceLoader<DbMapperProvider> providerLoader;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbMapperManager build() {
|
||||
return new DbMapperManagerImpl(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a mapper provider.
|
||||
*
|
||||
* @param provider prioritized provider
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public Builder addMapperProvider(DbMapperProvider provider) {
|
||||
this.providers.addService(provider);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a mapper provider with custom priority.
|
||||
*
|
||||
* @param provider provider
|
||||
* @param priority priority to use
|
||||
* @return updated builder instance
|
||||
* @see io.helidon.common.Prioritized
|
||||
* @see javax.annotation.Priority
|
||||
*/
|
||||
public Builder addMapperProvider(DbMapperProvider provider, int priority) {
|
||||
this.providers.addService(provider, priority);
|
||||
return this;
|
||||
}
|
||||
|
||||
// to be used by implementation
|
||||
List<DbMapperProvider> mapperProviders() {
|
||||
if (null == providerLoader) {
|
||||
return providers.build().asList();
|
||||
} else {
|
||||
return providerLoader.asList();
|
||||
}
|
||||
}
|
||||
|
||||
private Builder serviceLoader(HelidonServiceLoader<DbMapperProvider> serviceLoader) {
|
||||
this.providerLoader = serviceLoader;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.helidon.common.GenericType;
|
||||
import io.helidon.common.mapper.MapperException;
|
||||
import io.helidon.dbclient.spi.DbMapperProvider;
|
||||
|
||||
/**
|
||||
* Default implementation of the DbMapperManager.
|
||||
*/
|
||||
class DbMapperManagerImpl implements DbMapperManager {
|
||||
public static final String ERROR_NO_MAPPER_FOUND = "Failed to find DB mapper.";
|
||||
private final List<DbMapperProvider> providers;
|
||||
private final Map<Class<?>, DbMapper<?>> byClass = new ConcurrentHashMap<>();
|
||||
private final Map<GenericType<?>, DbMapper<?>> byType = new ConcurrentHashMap<>();
|
||||
|
||||
DbMapperManagerImpl(Builder builder) {
|
||||
this.providers = builder.mapperProviders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T read(DbRow row, Class<T> expectedType) {
|
||||
return executeMapping(() -> findMapper(expectedType, false)
|
||||
.read(row),
|
||||
row,
|
||||
TYPE_DB_ROW,
|
||||
GenericType.create(expectedType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T read(DbRow row, GenericType<T> expectedType) {
|
||||
return executeMapping(() -> findMapper(expectedType, false)
|
||||
.read(row),
|
||||
row,
|
||||
TYPE_DB_ROW,
|
||||
expectedType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Map<String, ?> toNamedParameters(T value, Class<T> valueClass) {
|
||||
return executeMapping(() -> findMapper(valueClass, false)
|
||||
.toNamedParameters(value),
|
||||
value,
|
||||
GenericType.create(valueClass),
|
||||
TYPE_NAMED_PARAMS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<?> toIndexedParameters(T value, Class<T> valueClass) {
|
||||
return executeMapping(() -> findMapper(valueClass, false)
|
||||
.toIndexedParameters(value),
|
||||
value,
|
||||
GenericType.create(valueClass),
|
||||
TYPE_INDEXED_PARAMS);
|
||||
}
|
||||
|
||||
private <T> T executeMapping(Supplier<T> mapping, Object source, GenericType<?> sourceType, GenericType<?> targetType) {
|
||||
try {
|
||||
return mapping.get();
|
||||
} catch (MapperException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw createMapperException(source, sourceType, targetType, e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> DbMapper<T> findMapper(Class<T> type, boolean fromTypes) {
|
||||
DbMapper<?> mapper = byClass.computeIfAbsent(type, aClass -> {
|
||||
return fromProviders(type)
|
||||
.orElseGet(() -> {
|
||||
GenericType<T> targetType = GenericType.create(type);
|
||||
if (fromTypes) {
|
||||
return notFoundMapper(targetType);
|
||||
}
|
||||
return findMapper(targetType, true);
|
||||
});
|
||||
});
|
||||
|
||||
return (DbMapper<T>) mapper;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> DbMapper<T> findMapper(GenericType<T> type, boolean fromClasses) {
|
||||
DbMapper<?> mapper = byType.computeIfAbsent(type, aType -> {
|
||||
return fromProviders(type)
|
||||
.orElseGet(() -> {
|
||||
if (!fromClasses && type.isClass()) {
|
||||
return findMapper((Class<T>) type.rawType(), true);
|
||||
}
|
||||
return notFoundMapper(type);
|
||||
});
|
||||
});
|
||||
|
||||
return (DbMapper<T>) mapper;
|
||||
}
|
||||
|
||||
private <T> Optional<DbMapper<T>> fromProviders(Class<T> type) {
|
||||
return providers.stream()
|
||||
.flatMap(provider -> provider.mapper(type).stream()).findFirst();
|
||||
}
|
||||
|
||||
private <T> Optional<DbMapper<T>> fromProviders(GenericType<T> type) {
|
||||
return providers.stream()
|
||||
.flatMap(provider -> provider.mapper(type).stream()).findFirst();
|
||||
}
|
||||
|
||||
private RuntimeException createMapperException(Object source,
|
||||
GenericType<?> sourceType,
|
||||
GenericType<?> targetType,
|
||||
Throwable throwable) {
|
||||
|
||||
throw new MapperException(sourceType,
|
||||
targetType,
|
||||
"Failed to map source of class '" + source.getClass().getName() + "'",
|
||||
throwable);
|
||||
}
|
||||
|
||||
private static <T> DbMapper<T> notFoundMapper(GenericType<T> type) {
|
||||
return new DbMapper<T>() {
|
||||
@Override
|
||||
public T read(DbRow row) {
|
||||
throw new MapperException(TYPE_DB_ROW, type, ERROR_NO_MAPPER_FOUND);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ?> toNamedParameters(T value) {
|
||||
throw new MapperException(type, TYPE_NAMED_PARAMS, ERROR_NO_MAPPER_FOUND);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<?> toIndexedParameters(T value) {
|
||||
throw new MapperException(type, TYPE_INDEXED_PARAMS, ERROR_NO_MAPPER_FOUND);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* {@link DbExecute#createNamedStatement(String)} (and other generic statements) execution result.
|
||||
* This is used when we do not know in advance whether we execute a query or a DML statement (such as insert, update, delete).
|
||||
* <p>
|
||||
* This class represents a future of two possible types - either a DML result returning the
|
||||
* number of changed rows (objects - depending on database type), or a query result returning the
|
||||
* {@link DbRows}.
|
||||
* <p>
|
||||
* One of the consumers on this interface is called as soon as it is known what type of statement was
|
||||
* executed - for SQL this would be when we finish execution of the prepared statement.
|
||||
* <p>
|
||||
* Alternative (or in combination) is to use the methods that return {@link java.util.concurrent.CompletionStage}
|
||||
* to process the results when (and if) they are done.
|
||||
*/
|
||||
public interface DbResult {
|
||||
/**
|
||||
* For DML statements, number of changed rows/objects is provided as soon as the statement completes.
|
||||
*
|
||||
* @param consumer consumer that eventually receives the count
|
||||
* @return DbResult to continue with processing a possible query result
|
||||
*/
|
||||
DbResult whenDml(Consumer<Long> consumer);
|
||||
|
||||
/**
|
||||
* For query statements, {@link DbRows} is provided as soon as the statement completes.
|
||||
* For example in SQL, this would be the time we get the ResultSet from the database. Nevertheless the
|
||||
* rows may not be read ({@link DbRows} itself represents a future of rows)
|
||||
*
|
||||
* @param consumer consumer that eventually processes the query result
|
||||
* @return DbResult to continue with processing a possible dml result
|
||||
*/
|
||||
DbResult whenRs(Consumer<DbRows<DbRow>> consumer);
|
||||
|
||||
/**
|
||||
* In case any exception occurs during processing, the handler is invoked.
|
||||
*
|
||||
* @param exceptionHandler handler to handle exceptional cases when processing the asynchronous request
|
||||
* @return DbResult ot continue with other methods
|
||||
*/
|
||||
DbResult exceptionally(Consumer<Throwable> exceptionHandler);
|
||||
|
||||
/**
|
||||
* This future completes if (and only if) the statement was a DML statement.
|
||||
* In case of any exception before the identification of statement type, all of
|
||||
* {@link #dmlFuture()}, {@link #rsFuture()} finish exceptionally, and {@link #exceptionFuture()} completes with the
|
||||
* exception.
|
||||
* In case the exception occurs after the identification of statement type, such as when
|
||||
* processing a result set of a query, only the {@link #exceptionFuture()}
|
||||
* completes. Exceptions that occur during processing of result set are handled by
|
||||
* methods in the {@link DbRows}.
|
||||
*
|
||||
* @return future for the DML result
|
||||
*/
|
||||
CompletionStage<Long> dmlFuture();
|
||||
|
||||
/**
|
||||
* This future completes if (and only if) the statement was a query statement.
|
||||
* In case of any exception before the identification of statement type, all of
|
||||
* {@link #dmlFuture()}, {@link #rsFuture()} finish exceptionally, and {@link #exceptionFuture()} completes with the
|
||||
* exception.
|
||||
* In case the exception occurs after the identification of statement type, such as when
|
||||
* processing a result set of a query, only the {@link #exceptionFuture()}
|
||||
* completes. Exceptions that occur during processing of result set are handled by
|
||||
* methods in the {@link DbRows}.
|
||||
*
|
||||
* @return future for the query result
|
||||
*/
|
||||
CompletionStage<DbRows<DbRow>> rsFuture();
|
||||
|
||||
/**
|
||||
* This future completes if (and only if) the statement finished with an exception, either
|
||||
* when executing the statement, or when processing the result set.
|
||||
*
|
||||
* @return future for an exceptional result
|
||||
*/
|
||||
CompletionStage<Throwable> exceptionFuture();
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.helidon.common.GenericType;
|
||||
import io.helidon.common.mapper.MapperException;
|
||||
|
||||
/**
|
||||
* Representation of a single row in a database (in SQL this would be a row, in a Document DB, this would be a single document).
|
||||
*/
|
||||
public interface DbRow {
|
||||
/**
|
||||
* Get a column in this row. Column is identified by its name.
|
||||
*
|
||||
* @param name column name
|
||||
* @return a column in this row
|
||||
*/
|
||||
DbColumn column(String name);
|
||||
|
||||
/**
|
||||
* Get a column in this row. Column is identified by its index.
|
||||
*
|
||||
* @param index column index starting from {@code 1}
|
||||
* @return a column in this row
|
||||
*/
|
||||
DbColumn column(int index);
|
||||
|
||||
/**
|
||||
* Iterate through each column in this row.
|
||||
*
|
||||
* @param columnAction what to do with each column
|
||||
*/
|
||||
void forEach(Consumer<? super DbColumn> columnAction);
|
||||
|
||||
/**
|
||||
* Get specific class instance representation of this row.
|
||||
* Mapper for target class must be already registered.
|
||||
*
|
||||
* @param <T> type of the returned value
|
||||
* @param type class of the returned value type
|
||||
* @return instance of requested class containing this database row
|
||||
* @throws MapperException in case the mapping is not defined or fails
|
||||
*/
|
||||
<T> T as(Class<T> type) throws MapperException;
|
||||
|
||||
/**
|
||||
* Map this row to an object using a {@link io.helidon.dbclient.DbMapper}.
|
||||
*
|
||||
* @param type type that supports generic declarations
|
||||
* @param <T> type to be returned
|
||||
* @return typed row
|
||||
* @throws MapperException in case the mapping is not defined or fails
|
||||
* @throws MapperException in case the mapping is not defined or fails
|
||||
*/
|
||||
<T> T as(GenericType<T> type) throws MapperException;
|
||||
|
||||
/**
|
||||
* Get specific class instance representation of this row.
|
||||
* Mapper for target class is provided as an argument.
|
||||
*
|
||||
* @param <T> type of the returned value
|
||||
* @param mapper method to create an target class instance from {@link DbRow}
|
||||
* @return instance of requested class containing this database row
|
||||
*/
|
||||
<T> T as(Function<DbRow, T> mapper);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.Flow;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.helidon.common.GenericType;
|
||||
|
||||
/**
|
||||
* Execution result containing result set with multiple rows.
|
||||
*
|
||||
* @param <T> type of the result, starts as {@link io.helidon.dbclient.DbRow}
|
||||
*/
|
||||
public interface DbRows<T> {
|
||||
/**
|
||||
* Map this row result using a mapping function.
|
||||
*
|
||||
* @param mapper mapping function
|
||||
* @param <U> new type of the row result
|
||||
* @return row result of the correct type
|
||||
*/
|
||||
<U> DbRows<U> map(Function<T, U> mapper);
|
||||
|
||||
/**
|
||||
* Map this row result using a configured {@link io.helidon.common.mapper.Mapper} that can
|
||||
* map current type to the desired type.
|
||||
*
|
||||
* <p>
|
||||
* The first mapping for row results of type {@link io.helidon.dbclient.DbRow} will also try to locate
|
||||
* appropriate mapper using {@link io.helidon.dbclient.DbMapperManager}.
|
||||
*
|
||||
* @param type class to map values to
|
||||
* @param <U> new type of the row result
|
||||
* @return row result of the correct type
|
||||
*/
|
||||
<U> DbRows<U> map(Class<U> type);
|
||||
|
||||
/**
|
||||
* Map this row result using a configured {@link io.helidon.common.mapper.Mapper} that can
|
||||
* map current type to the desired type.
|
||||
* <p>
|
||||
* The first mapping for row results of type {@link io.helidon.dbclient.DbRow} will also try to locate
|
||||
* appropriate mapper using {@link io.helidon.dbclient.DbMapperManager}.
|
||||
*
|
||||
* @param type generic type to map values to
|
||||
* @param <U> new type of the row result
|
||||
* @return row result of the target type
|
||||
*/
|
||||
<U> DbRows<U> map(GenericType<U> type);
|
||||
|
||||
/**
|
||||
* Get this result as a publisher of rows mapped to the correct type.
|
||||
*
|
||||
* @return publisher
|
||||
*/
|
||||
Flow.Publisher<T> publisher();
|
||||
|
||||
/**
|
||||
* Collect all the results into a list of rows mapped to the correct type.
|
||||
* <p><b>This is a dangerous operation, as it collects all results in memory. Use with care.</b>
|
||||
* @return future with the list
|
||||
*/
|
||||
CompletionStage<List<T>> collect();
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
/**
|
||||
* Database statement that can process parameters.
|
||||
* Method {@link #execute()} processes the statement and returns appropriate response.
|
||||
* <p>
|
||||
* All methods are non-blocking. The {@link #execute()} method returns either a {@link java.util.concurrent.CompletionStage}
|
||||
* or another object that provides similar API for eventual processing of the response.
|
||||
* <p>
|
||||
* Once parameters are set using one of the {@code params} methods, all other methods throw an
|
||||
* {@link IllegalStateException}.
|
||||
* <p>
|
||||
* Once a parameter is added using {@link #addParam(Object)} or {@link #addParam(String, Object)}, all other
|
||||
* {@code params} methods throw an {@link IllegalStateException}.
|
||||
* <p>
|
||||
* Once {@link #execute()} is called, all methods would throw an {@link IllegalStateException}.
|
||||
*
|
||||
* @param <R> Type of the result of this statement (e.g. a {@link java.util.concurrent.CompletionStage})
|
||||
* @param <D> Type of the descendant of this class
|
||||
*/
|
||||
public interface DbStatement<D extends DbStatement<D, R>, R> {
|
||||
/**
|
||||
* Configure parameters from a {@link java.util.List} by order.
|
||||
* The statement must use indexed parameters and configure them by order in the provided array.
|
||||
*
|
||||
* @param parameters ordered parameters to set on this statement, never null
|
||||
* @return updated db statement
|
||||
*/
|
||||
D params(List<?> parameters);
|
||||
|
||||
/**
|
||||
* Configure parameters from an array by order.
|
||||
* The statement must use indexed parameters and configure them by order in the provided array.
|
||||
*
|
||||
* @param parameters ordered parameters to set on this statement
|
||||
* @return updated db statement
|
||||
*/
|
||||
default D params(Object... parameters) {
|
||||
return params(Arrays.asList(parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure named parameters.
|
||||
* The statement must use named parameters and configure them from the provided map.
|
||||
*
|
||||
* @param parameters named parameters to set on this statement
|
||||
* @return updated db statement
|
||||
*/
|
||||
D params(Map<String, ?> parameters);
|
||||
|
||||
/**
|
||||
* Configure parameters using {@link Object} instance with registered mapper.
|
||||
* The statement must use named parameters and configure them from the map provided by mapper.
|
||||
*
|
||||
* @param parameters {@link Object} instance containing parameters
|
||||
* @return updated db statement
|
||||
*/
|
||||
D namedParam(Object parameters);
|
||||
|
||||
/**
|
||||
* Configure parameters using {@link Object} instance with registered mapper.
|
||||
* The statement must use indexed parameters and configure them by order in the array provided by mapper.
|
||||
*
|
||||
* @param parameters {@link Object} instance containing parameters
|
||||
* @return updated db statement
|
||||
*/
|
||||
D indexedParam(Object parameters);
|
||||
|
||||
/**
|
||||
* Add next parameter to the list of ordered parameters (e.g. the ones that use {@code ?} in SQL).
|
||||
*
|
||||
* @param parameter next parameter to set on this statement
|
||||
* @return updated db statement
|
||||
*/
|
||||
D addParam(Object parameter);
|
||||
|
||||
/**
|
||||
* Add next parameter to the map of named parameters (e.g. the ones that use {@code :name} in Helidon
|
||||
* JDBC SQL integration).
|
||||
*
|
||||
* @param name name of parameter
|
||||
* @param parameter value of parameter
|
||||
* @return updated db statement
|
||||
*/
|
||||
D addParam(String name, Object parameter);
|
||||
|
||||
/**
|
||||
* Execute this statement using the parameters configured with {@code params} and {@code addParams} methods.
|
||||
*
|
||||
* @return The future with result of this statement, as soon as the statement is executed; note that for queries
|
||||
* this is before the results are actually obtained from the database
|
||||
*/
|
||||
CompletionStage<R> execute();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
/**
|
||||
* DML Database statement.
|
||||
* A DML statement modifies records in the database and returns the number of modified records.
|
||||
*/
|
||||
public interface DbStatementDml extends DbStatement<DbStatementDml, Long> {
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
/**
|
||||
* A database statement that has unknown type (query or DML).
|
||||
*/
|
||||
public interface DbStatementGeneric extends DbStatement<DbStatementGeneric, DbResult> {
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Database statement that queries the database and returns a single row if present, or an empty optional.
|
||||
* In case the statement returns more than one rows, the future returned by {@link #execute()} will end in
|
||||
* {@link java.util.concurrent.CompletionStage#exceptionally(java.util.function.Function)}.
|
||||
*/
|
||||
public interface DbStatementGet extends DbStatement<DbStatementGet, Optional<DbRow>> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
/**
|
||||
* Database query statement.
|
||||
*/
|
||||
public interface DbStatementQuery extends DbStatement<DbStatementQuery, DbRows<DbRow>> {
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
/**
|
||||
* Usual supported statement types.
|
||||
*/
|
||||
public enum DbStatementType {
|
||||
/**
|
||||
* Query is statement that returns zero or more results.
|
||||
*/
|
||||
QUERY("q"),
|
||||
/**
|
||||
* Get is a statement that returns zero or one results.
|
||||
*/
|
||||
GET("g"),
|
||||
/**
|
||||
* Insert is a statements that creates new records.
|
||||
*/
|
||||
INSERT("i"),
|
||||
/**
|
||||
* Update is a statement that updates existing records.
|
||||
*/
|
||||
UPDATE("u"),
|
||||
/**
|
||||
* Delete is a statement that deletes existing records.
|
||||
*/
|
||||
DELETE("d"),
|
||||
/**
|
||||
* Generic DML statement.
|
||||
*/
|
||||
DML("dml"),
|
||||
/**
|
||||
* Database command not related to a specific collection.
|
||||
*/
|
||||
COMMAND("c"),
|
||||
/**
|
||||
* The statement type is not yet knows (e.g. when invoking
|
||||
* {@link DbExecute#createNamedStatement(String)})
|
||||
*/
|
||||
UNKNOWN("x");
|
||||
|
||||
private final String prefix;
|
||||
|
||||
DbStatementType(String prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Short prefix of this type.
|
||||
* This is used when generating a name for an unnamed statement.
|
||||
*
|
||||
* @return short prefix defining this type (should be very short)
|
||||
*/
|
||||
public String prefix() {
|
||||
return prefix;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
|
||||
/**
|
||||
* Configuration of statements to be used by database provider.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface DbStatements {
|
||||
/**
|
||||
* Get statement text for a named statement.
|
||||
*
|
||||
* @param name name of the statement
|
||||
* @return text of the statement (such as SQL code for SQL-based database statements)
|
||||
* @throws DbClientException in case the statement name does not exist
|
||||
*/
|
||||
String statement(String name) throws DbClientException;
|
||||
|
||||
/**
|
||||
* Builder of statements.
|
||||
*
|
||||
* @return a builder to customize statements
|
||||
*/
|
||||
static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create statements from configuration.
|
||||
* Statement configuration is expected to be a map of name to statement pairs.
|
||||
*
|
||||
* @param config configuration of the statements
|
||||
* @return statements as read from the configuration
|
||||
*/
|
||||
static DbStatements create(Config config) {
|
||||
return DbStatements.builder()
|
||||
.config(config)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent API builder for {@link io.helidon.dbclient.DbStatements}.
|
||||
*/
|
||||
class Builder implements io.helidon.common.Builder<DbStatements> {
|
||||
private final Map<String, String> configuredStatements = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Add named database statement to database configuration..
|
||||
*
|
||||
* @param name database statement name
|
||||
* @param statement database statement {@link String}
|
||||
* @return database provider builder
|
||||
*/
|
||||
public Builder addStatement(String name, String statement) {
|
||||
Objects.requireNonNull(name, "Statement name must be provided");
|
||||
Objects.requireNonNull(statement, "Statement body must be provided");
|
||||
configuredStatements.put(name, statement);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set statements from configuration. Each key in the current node is treated as a name of the statement,
|
||||
* each value as the statement content.
|
||||
*
|
||||
* @param config config node located on correct node
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public Builder config(Config config) {
|
||||
config.detach().asMap()
|
||||
.ifPresent(configuredStatements::putAll);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatements build() {
|
||||
return new DbStatements() {
|
||||
private final Map<String, String> statements = new HashMap<>(configuredStatements);
|
||||
|
||||
@Override
|
||||
public String statement(String name) {
|
||||
String statement = statements.get(name);
|
||||
|
||||
if (null == statement) {
|
||||
throw new DbClientException("Statement named '" + name + "' is not defined");
|
||||
}
|
||||
|
||||
return statement;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient;
|
||||
|
||||
/**
|
||||
* Database transaction.
|
||||
* Holds a single transaction to the database (if supported).
|
||||
* The transaction completes once {@link io.helidon.dbclient.DbClient#inTransaction(java.util.function.Function)} returns
|
||||
* the result provided by the body of the lambda within it.
|
||||
*/
|
||||
public interface DbTransaction extends DbExecute {
|
||||
/**
|
||||
* Configure this transaction to (eventually) rollback.
|
||||
*/
|
||||
void rollback();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Reactive Database API for Helidon.
|
||||
*/
|
||||
package io.helidon.dbclient;
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.spi;
|
||||
|
||||
/**
|
||||
* Java Service loader interface that provides drivers for a database (or a set of databases).
|
||||
*/
|
||||
public interface DbClientProvider {
|
||||
|
||||
/**
|
||||
* Name of this provider. This is used to find correct provider when using configuration only approach.
|
||||
*
|
||||
* @return provider name (such as {@code jdbc} or {@code mongo}
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* The implementation should provide its implementation of the {@link DbClientProviderBuilder}.
|
||||
*
|
||||
* @return a new builder instance
|
||||
*/
|
||||
DbClientProviderBuilder<?> builder();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.spi;
|
||||
|
||||
import io.helidon.common.Builder;
|
||||
import io.helidon.common.GenericType;
|
||||
import io.helidon.common.mapper.MapperManager;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.dbclient.DbClient;
|
||||
import io.helidon.dbclient.DbInterceptor;
|
||||
import io.helidon.dbclient.DbMapper;
|
||||
import io.helidon.dbclient.DbStatementType;
|
||||
import io.helidon.dbclient.DbStatements;
|
||||
|
||||
/**
|
||||
* Database provider builder.
|
||||
*
|
||||
* @param <T> type of the builder extending implementing this interface.
|
||||
*/
|
||||
public interface DbClientProviderBuilder<T extends DbClientProviderBuilder<T>> extends Builder<DbClient> {
|
||||
|
||||
/**
|
||||
* Use database connection configuration from configuration file.
|
||||
*
|
||||
* @param config {@link io.helidon.config.Config} instance with database connection attributes
|
||||
* @return database provider builder
|
||||
*/
|
||||
T config(Config config);
|
||||
|
||||
/**
|
||||
* Set database connection string (URL).
|
||||
*
|
||||
* @param url database connection string
|
||||
* @return database provider builder
|
||||
*/
|
||||
T url(String url);
|
||||
|
||||
/**
|
||||
* Set database connection user name.
|
||||
*
|
||||
* @param username database connection user name
|
||||
* @return database provider builder
|
||||
*/
|
||||
T username(String username);
|
||||
|
||||
/**
|
||||
* Set database connection p¨assword.
|
||||
*
|
||||
* @param password database connection password
|
||||
* @return database provider builder
|
||||
*/
|
||||
T password(String password);
|
||||
|
||||
/**
|
||||
* Statements to use either from configuration
|
||||
* or manually configured.
|
||||
*
|
||||
* @param statements Statements to use
|
||||
* @return updated builder instance
|
||||
*/
|
||||
T statements(DbStatements statements);
|
||||
|
||||
/**
|
||||
* Database schema mappers provider.
|
||||
*
|
||||
* @param provider database schema mappers provider to use
|
||||
* @return updated builder instance
|
||||
*/
|
||||
T addMapperProvider(DbMapperProvider provider);
|
||||
|
||||
/**
|
||||
* Add a custom mapper.
|
||||
*
|
||||
* @param dbMapper the mapper capable of mapping the mappedClass to various database objects
|
||||
* @param mappedClass class that this mapper supports
|
||||
* @param <TYPE> type of the supported class
|
||||
* @return updated builder instance.
|
||||
*/
|
||||
<TYPE> T addMapper(DbMapper<TYPE> dbMapper, Class<TYPE> mappedClass);
|
||||
|
||||
/**
|
||||
* Add a custom mapper with generic types support.
|
||||
*
|
||||
* @param dbMapper the mapper capable of mapping the mappedClass to various database objects
|
||||
* @param mappedType type that this mapper supports
|
||||
* @param <TYPE> type of the supported class
|
||||
* @return updated builder instance.
|
||||
*/
|
||||
<TYPE> T addMapper(DbMapper<TYPE> dbMapper, GenericType<TYPE> mappedType);
|
||||
|
||||
/**
|
||||
* Mapper manager for generic mapping, such as mapping of parameters to expected types.
|
||||
*
|
||||
* @param manager mapper manager
|
||||
* @return updated builder instance
|
||||
*/
|
||||
T mapperManager(MapperManager manager);
|
||||
|
||||
/**
|
||||
* Add an interceptor.
|
||||
* This allows to add implementation of tracing, metrics, logging etc. without the need to hard-code these into
|
||||
* the base.
|
||||
*
|
||||
* @param interceptor interceptor instance
|
||||
* @return updated builder instance
|
||||
*/
|
||||
T addInterceptor(DbInterceptor interceptor);
|
||||
|
||||
/**
|
||||
* Add an interceptor that is active only on the configured statement names.
|
||||
* This interceptor is only executed on named statements.
|
||||
*
|
||||
* @param interceptor interceptor instance
|
||||
* @param statementNames statement names to be active on
|
||||
* @return updated builder instance
|
||||
*/
|
||||
T addInterceptor(DbInterceptor interceptor, String... statementNames);
|
||||
|
||||
/**
|
||||
* Add an interceptor thas is active only on configured statement types.
|
||||
* This interceptor is executed on all statements of that type.
|
||||
* <p>
|
||||
* Note the specific handling of the following types:
|
||||
* <ul>
|
||||
* <li>{@link io.helidon.dbclient.DbStatementType#DML} - used only when the statement is created as a DML statement
|
||||
* such as when using {@link io.helidon.dbclient.DbExecute#createDmlStatement(String)}
|
||||
* (this interceptor would not be enabled for inserts, updates, deletes)</li>
|
||||
* <li>{@link io.helidon.dbclient.DbStatementType#UNKNOWN} - used only when the statement is created as a general statement
|
||||
* such as when using {@link io.helidon.dbclient.DbExecute#createStatement(String)}
|
||||
* (this interceptor would not be enabled for any other statements)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param interceptor interceptor instance
|
||||
* @param dbStatementTypes statement types to be active on
|
||||
* @return updated builder instance
|
||||
*/
|
||||
T addInterceptor(DbInterceptor interceptor, DbStatementType... dbStatementTypes);
|
||||
|
||||
/**
|
||||
* Build database handler for specific provider.
|
||||
*
|
||||
* @return database handler instance
|
||||
*/
|
||||
@Override
|
||||
DbClient build();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.spi;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.dbclient.DbInterceptor;
|
||||
|
||||
/**
|
||||
* Java service loader service to configure interceptors.
|
||||
*/
|
||||
public interface DbInterceptorProvider {
|
||||
|
||||
/**
|
||||
* The configuration key expected in config.
|
||||
* If the key exists, the builder looks into
|
||||
* {@code global}, {@code named}, and {@code typed} subkeys
|
||||
* to configure appropriate instances.
|
||||
* Method {@link #create(io.helidon.config.Config)} is called for each
|
||||
* configuration as follows:
|
||||
* <ul>
|
||||
* <li>{@code global}: the configuration key is used to get a new instance</li>
|
||||
* <li>{code named}: for each configuration node with a list of nodes, a new instance is requested</li>
|
||||
* <li>{code typed}: for each configuration node with a list of types, a new instance is requested</li>
|
||||
* </ul>
|
||||
* @return name of the configuration key (such as "tracing")
|
||||
*/
|
||||
String configKey();
|
||||
|
||||
/**
|
||||
* Create a new interceptor instance with the configuration provided.
|
||||
*
|
||||
* @param config configuration node with additional properties that are (maybe) configured for this interceptor
|
||||
* @return an interceptor to handle DB statements
|
||||
*/
|
||||
DbInterceptor create(Config config);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.spi;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import io.helidon.common.GenericType;
|
||||
import io.helidon.dbclient.DbMapper;
|
||||
|
||||
/**
|
||||
* Java Service loader interface for database mappers.
|
||||
*
|
||||
* @see io.helidon.dbclient.DbMapper
|
||||
*/
|
||||
public interface DbMapperProvider {
|
||||
/**
|
||||
* Returns mapper for specific type.
|
||||
*
|
||||
* @param <T> target mapping type
|
||||
* @param type class of the returned mapper type
|
||||
* @return a mapper for the specified type or empty
|
||||
*/
|
||||
<T> Optional<DbMapper<T>> mapper(Class<T> type);
|
||||
|
||||
/**
|
||||
* Returns mapper for specific type supporting generic types as well.
|
||||
* To get a list of strings: {@code mapper(new GenericType<List<String>>(){})}
|
||||
*
|
||||
* @param type type to find mapper for
|
||||
* @param <T> type of the response
|
||||
* @return a mapper for the specified type or empty
|
||||
*/
|
||||
default <T> Optional<DbMapper<T>> mapper(GenericType<T> type) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
/**
|
||||
* Service provider interface for Helidon DB.
|
||||
* The main entry point for driver implementor is {@link io.helidon.dbclient.spi.DbClientProvider}.
|
||||
*
|
||||
* @see io.helidon.dbclient.spi.DbInterceptorProvider
|
||||
* @see io.helidon.dbclient.spi.DbMapperProvider
|
||||
*/
|
||||
package io.helidon.dbclient.spi;
|
||||
36
dbclient/dbclient/src/main/java/module-info.java
Normal file
36
dbclient/dbclient/src/main/java/module-info.java
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helidon DB Client.
|
||||
*
|
||||
* @see io.helidon.dbclient.DbClient
|
||||
*/
|
||||
module io.helidon.dbclient {
|
||||
requires java.logging;
|
||||
requires transitive io.helidon.config;
|
||||
requires transitive io.helidon.common;
|
||||
requires transitive io.helidon.common.context;
|
||||
requires transitive io.helidon.common.mapper;
|
||||
requires transitive io.helidon.common.serviceloader;
|
||||
|
||||
exports io.helidon.dbclient;
|
||||
exports io.helidon.dbclient.spi;
|
||||
|
||||
uses io.helidon.dbclient.spi.DbClientProvider;
|
||||
uses io.helidon.dbclient.spi.DbMapperProvider;
|
||||
uses io.helidon.dbclient.spi.DbInterceptorProvider;
|
||||
}
|
||||
43
dbclient/health/pom.xml
Normal file
43
dbclient/health/pom.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
Licensed 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient-project</artifactId>
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>helidon-dbclient-health</artifactId>
|
||||
<name>Helidon DB Client Health</name>
|
||||
|
||||
<description>Health check for Helidon DB Client</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.health</groupId>
|
||||
<artifactId>helidon-health</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.health;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import io.helidon.common.HelidonFeatures;
|
||||
import io.helidon.common.HelidonFlavor;
|
||||
import io.helidon.dbclient.DbClient;
|
||||
|
||||
import org.eclipse.microprofile.health.HealthCheck;
|
||||
import org.eclipse.microprofile.health.HealthCheckResponse;
|
||||
import org.eclipse.microprofile.health.HealthCheckResponseBuilder;
|
||||
|
||||
/**
|
||||
* Database health check.
|
||||
*/
|
||||
public final class DbClientHealthCheck implements HealthCheck {
|
||||
static {
|
||||
HelidonFeatures.register(HelidonFlavor.SE, "DbClient", "HealthCheck");
|
||||
}
|
||||
|
||||
private final DbClient dbClient;
|
||||
private final String name;
|
||||
|
||||
private DbClientHealthCheck(Builder builder) {
|
||||
this.dbClient = builder.database;
|
||||
this.name = builder.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a health check for the database.
|
||||
*
|
||||
* @param dbClient A database that implements {@link io.helidon.dbclient.DbClient#ping()}
|
||||
* @return health check that can be used with
|
||||
* {@link io.helidon.health.HealthSupport.Builder#add(org.eclipse.microprofile.health.HealthCheck...)}
|
||||
*/
|
||||
public static DbClientHealthCheck create(DbClient dbClient) {
|
||||
return builder(dbClient).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* A fluent API builder to create a fully customized database health check.
|
||||
*
|
||||
* @param dbClient database
|
||||
* @return a new builder
|
||||
*/
|
||||
public static Builder builder(DbClient dbClient) {
|
||||
return new Builder(dbClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HealthCheckResponse call() {
|
||||
HealthCheckResponseBuilder builder = HealthCheckResponse.builder()
|
||||
.name(name);
|
||||
|
||||
AtomicReference<Throwable> throwable = new AtomicReference<>();
|
||||
try {
|
||||
dbClient.ping().toCompletableFuture()
|
||||
.exceptionally(theThrowable -> {
|
||||
throwable.set(theThrowable);
|
||||
return null;
|
||||
})
|
||||
.get();
|
||||
} catch (Throwable e) {
|
||||
builder.down();
|
||||
throwable.set(e);
|
||||
}
|
||||
|
||||
Throwable thrown = throwable.get();
|
||||
|
||||
if (null == thrown) {
|
||||
builder.up();
|
||||
} else {
|
||||
thrown = thrown.getCause();
|
||||
builder.down();
|
||||
builder.withData("ErrorMessage", thrown.getMessage());
|
||||
builder.withData("ErrorClass", thrown.getClass().getName());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent API builder for {@link DbClientHealthCheck}.
|
||||
*/
|
||||
public static final class Builder implements io.helidon.common.Builder<DbClientHealthCheck> {
|
||||
private final DbClient database;
|
||||
private String name;
|
||||
|
||||
private Builder(DbClient database) {
|
||||
this.database = database;
|
||||
this.name = database.dbType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbClientHealthCheck build() {
|
||||
return new DbClientHealthCheck(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customized name of the health check.
|
||||
* Default uses {@link io.helidon.dbclient.DbClient#dbType()}.
|
||||
*
|
||||
* @param name name of the health check
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public Builder name(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
/**
|
||||
* Health check support for Helidon DB Client.
|
||||
*/
|
||||
package io.helidon.dbclient.health;
|
||||
26
dbclient/health/src/main/java/module-info.java
Normal file
26
dbclient/health/src/main/java/module-info.java
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helidon DB Client Health Check.
|
||||
*/
|
||||
module io.helidon.dbclient.health {
|
||||
requires java.logging;
|
||||
requires io.helidon.dbclient;
|
||||
requires io.helidon.health;
|
||||
|
||||
exports io.helidon.dbclient.health;
|
||||
}
|
||||
33
dbclient/jdbc/etc/spotbugs/exclude.xml
Normal file
33
dbclient/jdbc/etc/spotbugs/exclude.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
Licensed 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.
|
||||
-->
|
||||
|
||||
<FindBugsFilter
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="https://github.com/spotbugs/filter/3.0.0"
|
||||
xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd">
|
||||
|
||||
<Match>
|
||||
<!-- False positive. See https://github.com/spotbugs/spotbugs/issues/756 -->
|
||||
<Class name="io.helidon.dbclient.jdbc.JdbcStatementQuery"/>
|
||||
<Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"/>
|
||||
</Match>
|
||||
<Match>
|
||||
<!-- False positive. See https://github.com/spotbugs/spotbugs/issues/756 -->
|
||||
<Class name="io.helidon.dbclient.jdbc.JdbcStatementQuery$RowPublisher"/>
|
||||
<Bug pattern="RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE"/>
|
||||
</Match>
|
||||
</FindBugsFilter>
|
||||
64
dbclient/jdbc/pom.xml
Normal file
64
dbclient/jdbc/pom.xml
Normal file
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
Licensed 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>helidon-dbclient-project</artifactId>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>helidon-dbclient-jdbc</artifactId>
|
||||
<name>Helidon DB Client JDBC</name>
|
||||
<description>Helidon DB implementation for JDBC</description>
|
||||
|
||||
<properties>
|
||||
<spotbugs.exclude>etc/spotbugs/exclude.xml</spotbugs.exclude>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.common</groupId>
|
||||
<artifactId>helidon-common-configurable</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.helidon.common.serviceloader.HelidonServiceLoader;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.dbclient.jdbc.spi.HikariCpExtensionProvider;
|
||||
|
||||
/**
|
||||
* JDBC Configuration parameters.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ConnectionPool {
|
||||
|
||||
/**
|
||||
* Create a JDBC connection pool from provided configuration.
|
||||
* <table>
|
||||
* <caption>Optional configuration parameters</caption>
|
||||
* <tr>
|
||||
* <th>key</th>
|
||||
* <th>default value</th>
|
||||
* <th>description</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>url</td>
|
||||
* <td> </td>
|
||||
* <td>JDBC URL of the database - this property is required when only configuration is used.
|
||||
* Example: {@code jdbc:mysql://127.0.0.1:3306/pokemon?useSSL=false}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>username</td>
|
||||
* <td> </td>
|
||||
* <td>Username used to connect to the database</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>password</td>
|
||||
* <td> </td>
|
||||
* <td>Password used to connect to the database</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* @param config configuration of connection pool
|
||||
* @return a new instance configured from the provided config
|
||||
*/
|
||||
static ConnectionPool create(Config config) {
|
||||
return ConnectionPool.builder()
|
||||
.config(config)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a fluent API builder for a JDBC Connection pool based on URL, username and password.
|
||||
* @return a new builder
|
||||
*/
|
||||
static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a connection from the pool.
|
||||
* The call to {@link java.sql.Connection#close()} should return that connection to the pool.
|
||||
* The connection pool should handle capacity issues and timeouts using unchecked exceptions thrown by this method.
|
||||
*
|
||||
* @return a connection read to execute statements
|
||||
*/
|
||||
Connection connection();
|
||||
|
||||
/**
|
||||
* The type of this database - if better details than {@value JdbcDbClientProvider#JDBC_DB_TYPE} is
|
||||
* available, return it. This could be "jdbc:mysql" etc.
|
||||
*
|
||||
* @return type of this database
|
||||
*/
|
||||
default String dbType() {
|
||||
return JdbcDbClientProvider.JDBC_DB_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent API builder for {@link io.helidon.dbclient.jdbc.ConnectionPool}.
|
||||
* The builder will produce a connection pool based on Hikari connection pool and will support
|
||||
* {@link io.helidon.dbclient.jdbc.spi.HikariCpExtensionProvider} to enhance the Hikari pool.
|
||||
*/
|
||||
final class Builder implements io.helidon.common.Builder<ConnectionPool> {
|
||||
/**
|
||||
* Database connection URL configuration key.
|
||||
*/
|
||||
static final String URL = "url";
|
||||
/**
|
||||
* Database connection user name configuration key.
|
||||
*/
|
||||
static final String USERNAME = "username";
|
||||
/**
|
||||
* Database connection user password configuration key.
|
||||
*/
|
||||
static final String PASSWORD = "password";
|
||||
/**
|
||||
* Database connection configuration key for Helidon specific
|
||||
* properties.
|
||||
*/
|
||||
static final String HELIDON_RESERVED_CONFIG_KEY = "helidon";
|
||||
|
||||
//jdbc:mysql://127.0.0.1:3306/pokemon?useSSL=false
|
||||
private static final Pattern URL_PATTERN = Pattern.compile("(\\w+:\\w+):.*");
|
||||
|
||||
private Properties properties = new Properties();
|
||||
|
||||
private String url;
|
||||
private String username;
|
||||
private String password;
|
||||
private Config extensionsConfig;
|
||||
private final HelidonServiceLoader.Builder<HikariCpExtensionProvider> extensionLoader = HelidonServiceLoader
|
||||
.builder(ServiceLoader.load(HikariCpExtensionProvider.class));
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionPool build() {
|
||||
final Matcher matcher = URL_PATTERN.matcher(url);
|
||||
String dbType = matcher.matches()
|
||||
? matcher.group(1)
|
||||
: JdbcDbClientProvider.JDBC_DB_TYPE;
|
||||
|
||||
return new HikariConnectionPool(this, dbType, extensions());
|
||||
}
|
||||
|
||||
private List<HikariCpExtension> extensions() {
|
||||
if (null == extensionsConfig) {
|
||||
extensionsConfig = Config.empty();
|
||||
}
|
||||
return extensionLoader.build()
|
||||
.asList()
|
||||
.stream()
|
||||
.map(provider -> provider.extension(extensionsConfig.get(provider.configKey())))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public Builder config(Config config) {
|
||||
Map<String, String> poolConfig = config.detach().asMap().get();
|
||||
poolConfig.forEach((key, value) -> {
|
||||
switch (key) {
|
||||
case URL:
|
||||
url(value);
|
||||
break;
|
||||
case USERNAME:
|
||||
username(value);
|
||||
break;
|
||||
case PASSWORD:
|
||||
password(value);
|
||||
break;
|
||||
default:
|
||||
if (!key.startsWith(HELIDON_RESERVED_CONFIG_KEY + ".")) {
|
||||
// all other properties are sent to the pool
|
||||
properties.setProperty(key, value);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
this.extensionsConfig = config.get(HELIDON_RESERVED_CONFIG_KEY);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder url(String url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder username(String username) {
|
||||
this.username = username;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder password(String password) {
|
||||
this.password = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder properties(Properties properties) {
|
||||
this.properties = properties;
|
||||
return this;
|
||||
}
|
||||
|
||||
Properties properties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
String url() {
|
||||
return url;
|
||||
}
|
||||
|
||||
String username() {
|
||||
return username;
|
||||
}
|
||||
|
||||
String password() {
|
||||
return password;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
import io.helidon.dbclient.DbClientException;
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
|
||||
/**
|
||||
* Hikari Connection Pool integration.
|
||||
*/
|
||||
public class HikariConnectionPool implements ConnectionPool {
|
||||
/** Hikari Connection Pool instance. */
|
||||
private final HikariDataSource dataSource;
|
||||
|
||||
/** The type of this database. */
|
||||
private final String dbType;
|
||||
|
||||
HikariConnectionPool(Builder builder, String dbType, List<HikariCpExtension> extensions) {
|
||||
this.dbType = dbType;
|
||||
HikariConfig config = new HikariConfig(builder.properties());
|
||||
config.setJdbcUrl(builder.url());
|
||||
config.setUsername(builder.username());
|
||||
config.setPassword(builder.password());
|
||||
// Apply configuration update from extensions
|
||||
extensions.forEach(interceptor -> {
|
||||
interceptor.configure(config);
|
||||
});
|
||||
this.dataSource = new HikariDataSource(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection connection() {
|
||||
try {
|
||||
return dataSource.getConnection();
|
||||
} catch (SQLException ex) {
|
||||
throw new DbClientException(
|
||||
String.format("Failed to create a connection to %s", dataSource.getJdbcUrl()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String dbType() {
|
||||
return dbType;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.jdbc;
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
|
||||
/**
|
||||
* Interceptor to handle connection pool configuration.
|
||||
*/
|
||||
public interface HikariCpExtension {
|
||||
/**
|
||||
* Set additional configuration option on DB client configuration.
|
||||
*
|
||||
* @param poolConfig client configuration instance
|
||||
*/
|
||||
void configure(HikariConfig poolConfig);
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import io.helidon.common.HelidonFeatures;
|
||||
import io.helidon.common.HelidonFlavor;
|
||||
import io.helidon.common.mapper.MapperManager;
|
||||
import io.helidon.dbclient.DbClient;
|
||||
import io.helidon.dbclient.DbClientException;
|
||||
import io.helidon.dbclient.DbExecute;
|
||||
import io.helidon.dbclient.DbMapperManager;
|
||||
import io.helidon.dbclient.DbStatementDml;
|
||||
import io.helidon.dbclient.DbStatementGeneric;
|
||||
import io.helidon.dbclient.DbStatementGet;
|
||||
import io.helidon.dbclient.DbStatementQuery;
|
||||
import io.helidon.dbclient.DbStatementType;
|
||||
import io.helidon.dbclient.DbStatements;
|
||||
import io.helidon.dbclient.DbTransaction;
|
||||
import io.helidon.dbclient.common.AbstractDbExecute;
|
||||
import io.helidon.dbclient.common.InterceptorSupport;
|
||||
|
||||
/**
|
||||
* Helidon DB implementation for JDBC drivers.
|
||||
*/
|
||||
class JdbcDbClient implements DbClient {
|
||||
static {
|
||||
HelidonFeatures.register(HelidonFlavor.SE, "DbClient", "JDBC");
|
||||
}
|
||||
|
||||
/** Local logger instance. */
|
||||
private static final Logger LOGGER = Logger.getLogger(DbClient.class.getName());
|
||||
|
||||
private final ExecutorService executorService;
|
||||
private final ConnectionPool connectionPool;
|
||||
private final DbStatements statements;
|
||||
private final DbMapperManager dbMapperManager;
|
||||
private final MapperManager mapperManager;
|
||||
private final InterceptorSupport interceptors;
|
||||
|
||||
JdbcDbClient(JdbcDbClientProviderBuilder builder) {
|
||||
this.executorService = builder.executorService();
|
||||
this.connectionPool = builder.connectionPool();
|
||||
this.statements = builder.statements();
|
||||
this.dbMapperManager = builder.dbMapperManager();
|
||||
this.mapperManager = builder.mapperManager();
|
||||
this.interceptors = builder.interceptors();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> CompletionStage<T> inTransaction(Function<DbTransaction, CompletionStage<T>> executor) {
|
||||
|
||||
JdbcTxExecute execute = new JdbcTxExecute(
|
||||
statements,
|
||||
executorService,
|
||||
interceptors,
|
||||
connectionPool,
|
||||
dbMapperManager,
|
||||
mapperManager);
|
||||
CompletionStage<T> stage = executor.apply(execute)
|
||||
.thenApply(it -> {
|
||||
execute.context().whenComplete()
|
||||
.thenAccept(nothing -> {
|
||||
LOGGER.finest(() -> "Transaction commit");
|
||||
execute.doCommit().exceptionally(RollbackHandler.create(execute, Level.WARNING));
|
||||
}).exceptionally(RollbackHandler.create(execute, Level.WARNING));
|
||||
return it;
|
||||
});
|
||||
|
||||
stage.exceptionally(RollbackHandler.create(execute, Level.FINEST));
|
||||
|
||||
return stage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Functional interface called to rollback failed transaction.
|
||||
*
|
||||
* @param <T> statement execution result type
|
||||
*/
|
||||
private static final class RollbackHandler<T> implements Function<Throwable, T> {
|
||||
|
||||
private final JdbcTxExecute execute;
|
||||
private final Level level;
|
||||
|
||||
private static RollbackHandler create(final JdbcTxExecute execute, final Level level) {
|
||||
return new RollbackHandler(execute, level);
|
||||
}
|
||||
|
||||
private RollbackHandler(final JdbcTxExecute execute, final Level level) {
|
||||
this.execute = execute;
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T apply(Throwable t) {
|
||||
LOGGER.log(level,
|
||||
String.format("Transaction rollback: %s", t.getMessage()),
|
||||
t);
|
||||
execute.doRollback().exceptionally(t2 -> {
|
||||
LOGGER.log(level,
|
||||
String.format("Transaction rollback failed: %s", t2.getMessage()),
|
||||
t2);
|
||||
return null;
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends CompletionStage<?>> T execute(Function<DbExecute, T> executor) {
|
||||
JdbcExecute execute = new JdbcExecute(statements,
|
||||
executorService,
|
||||
interceptors,
|
||||
connectionPool,
|
||||
dbMapperManager,
|
||||
mapperManager);
|
||||
|
||||
T resultFuture = executor.apply(execute);
|
||||
|
||||
resultFuture.thenApply(it -> {
|
||||
execute.context().whenComplete()
|
||||
.thenAccept(nothing -> {
|
||||
LOGGER.finest(() -> "Execution finished, closing connection");
|
||||
execute.close();
|
||||
}).exceptionally(throwable -> {
|
||||
LOGGER.log(Level.WARNING,
|
||||
String.format("Execution failed: %s", throwable.getMessage()),
|
||||
throwable);
|
||||
execute.close();
|
||||
return null;
|
||||
});
|
||||
return it;
|
||||
});
|
||||
|
||||
resultFuture.exceptionally(throwable -> {
|
||||
LOGGER.log(Level.FINEST,
|
||||
String.format("Execution failed: %s", throwable.getMessage()),
|
||||
throwable);
|
||||
execute.close();
|
||||
return null;
|
||||
});
|
||||
|
||||
return resultFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> ping() {
|
||||
return execute(exec -> exec.namedUpdate("ping"))
|
||||
// need to get from Long to Void
|
||||
.thenRun(() -> {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String dbType() {
|
||||
return connectionPool.dbType();
|
||||
}
|
||||
|
||||
private static final class JdbcTxExecute extends JdbcExecute implements DbTransaction {
|
||||
|
||||
private volatile boolean setRollbackOnly = false;
|
||||
|
||||
private JdbcTxExecute(DbStatements statements,
|
||||
ExecutorService executorService,
|
||||
InterceptorSupport interceptors,
|
||||
ConnectionPool connectionPool,
|
||||
DbMapperManager dbMapperManager,
|
||||
MapperManager mapperManager) {
|
||||
super(statements, createTxContext(executorService, interceptors, connectionPool, dbMapperManager, mapperManager));
|
||||
}
|
||||
|
||||
private static JdbcExecuteContext createTxContext(ExecutorService executorService,
|
||||
InterceptorSupport interceptors,
|
||||
ConnectionPool connectionPool,
|
||||
DbMapperManager dbMapperManager,
|
||||
MapperManager mapperManager) {
|
||||
CompletionStage<Connection> connection = CompletableFuture.supplyAsync(connectionPool::connection, executorService)
|
||||
.thenApply(conn -> {
|
||||
try {
|
||||
conn.setAutoCommit(false);
|
||||
} catch (SQLException e) {
|
||||
throw new DbClientException("Failed to set autocommit to false", e);
|
||||
}
|
||||
return conn;
|
||||
});
|
||||
|
||||
return JdbcExecuteContext.create(executorService,
|
||||
interceptors,
|
||||
connectionPool.dbType(),
|
||||
connection,
|
||||
dbMapperManager,
|
||||
mapperManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() {
|
||||
setRollbackOnly = true;
|
||||
}
|
||||
|
||||
private CompletionStage<Connection> doRollback() {
|
||||
return context().connection()
|
||||
.thenApply(conn -> {
|
||||
try {
|
||||
conn.rollback();
|
||||
conn.close();
|
||||
} catch (SQLException e) {
|
||||
throw new DbClientException("Failed to rollback a transaction, or close a connection", e);
|
||||
}
|
||||
|
||||
return conn;
|
||||
});
|
||||
}
|
||||
|
||||
private CompletionStage<Connection> doCommit() {
|
||||
if (setRollbackOnly) {
|
||||
return doRollback();
|
||||
}
|
||||
return context().connection()
|
||||
.thenApply(conn -> {
|
||||
try {
|
||||
conn.commit();
|
||||
conn.close();
|
||||
} catch (SQLException e) {
|
||||
throw new DbClientException("Failed to commit a transaction, or close a connection", e);
|
||||
}
|
||||
return conn;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static class JdbcExecute extends AbstractDbExecute {
|
||||
|
||||
private final JdbcExecuteContext context;
|
||||
|
||||
private JdbcExecute(DbStatements statements, JdbcExecuteContext context) {
|
||||
super(statements);
|
||||
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
private JdbcExecute(DbStatements statements,
|
||||
ExecutorService executorService,
|
||||
InterceptorSupport interceptors,
|
||||
ConnectionPool connectionPool,
|
||||
DbMapperManager dbMapperManager,
|
||||
MapperManager mapperManager) {
|
||||
this(statements, createContext(executorService, interceptors, connectionPool, dbMapperManager, mapperManager));
|
||||
}
|
||||
|
||||
private static JdbcExecuteContext createContext(ExecutorService executorService,
|
||||
InterceptorSupport interceptors,
|
||||
ConnectionPool connectionPool,
|
||||
DbMapperManager dbMapperManager,
|
||||
MapperManager mapperManager) {
|
||||
CompletionStage<Connection> connection = CompletableFuture.supplyAsync(connectionPool::connection, executorService)
|
||||
.thenApply(conn -> {
|
||||
try {
|
||||
conn.setAutoCommit(true);
|
||||
} catch (SQLException e) {
|
||||
throw new DbClientException("Failed to set autocommit to true", e);
|
||||
}
|
||||
return conn;
|
||||
});
|
||||
|
||||
return JdbcExecuteContext.create(executorService,
|
||||
interceptors,
|
||||
connectionPool.dbType(),
|
||||
connection,
|
||||
dbMapperManager,
|
||||
mapperManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementQuery createNamedQuery(String statementName, String statement) {
|
||||
return new JdbcStatementQuery(context,
|
||||
JdbcStatementContext.create(DbStatementType.QUERY, statementName, statement));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementGet createNamedGet(String statementName, String statement) {
|
||||
return new JdbcStatementGet(context,
|
||||
JdbcStatementContext.create(DbStatementType.GET, statementName, statement));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementDml createNamedDmlStatement(String statementName, String statement) {
|
||||
return new JdbcStatementDml(context,
|
||||
JdbcStatementContext.create(DbStatementType.DML, statementName, statement));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementDml createNamedInsert(String statementName, String statement) {
|
||||
return new JdbcStatementDml(context,
|
||||
JdbcStatementContext.create(DbStatementType.INSERT, statementName, statement));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementDml createNamedUpdate(String statementName, String statement) {
|
||||
return new JdbcStatementDml(context,
|
||||
JdbcStatementContext.create(DbStatementType.UPDATE, statementName, statement));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementDml createNamedDelete(String statementName, String statement) {
|
||||
return new JdbcStatementDml(context,
|
||||
JdbcStatementContext.create(DbStatementType.DELETE, statementName, statement));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbStatementGeneric createNamedStatement(String statementName, String statement) {
|
||||
return new JdbcStatementGeneric(context,
|
||||
JdbcStatementContext.create(DbStatementType.UNKNOWN, statementName, statement));
|
||||
}
|
||||
|
||||
JdbcExecuteContext context() {
|
||||
return context;
|
||||
}
|
||||
|
||||
void close() {
|
||||
context.connection()
|
||||
.thenAccept(conn -> {
|
||||
try {
|
||||
conn.close();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.log(Level.WARNING, String.format("Could not close connection: %s", e.getMessage()), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.jdbc;
|
||||
|
||||
import io.helidon.dbclient.spi.DbClientProvider;
|
||||
|
||||
/**
|
||||
* Provider for JDBC database implementation.
|
||||
*/
|
||||
public class JdbcDbClientProvider implements DbClientProvider {
|
||||
|
||||
static final String JDBC_DB_TYPE = "jdbc";
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return JDBC_DB_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcDbClientProviderBuilder builder() {
|
||||
return new JdbcDbClientProviderBuilder();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.jdbc;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.helidon.common.GenericType;
|
||||
import io.helidon.common.configurable.ThreadPoolSupplier;
|
||||
import io.helidon.common.mapper.MapperManager;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.dbclient.DbClient;
|
||||
import io.helidon.dbclient.DbClientException;
|
||||
import io.helidon.dbclient.DbInterceptor;
|
||||
import io.helidon.dbclient.DbMapper;
|
||||
import io.helidon.dbclient.DbMapperManager;
|
||||
import io.helidon.dbclient.DbStatementType;
|
||||
import io.helidon.dbclient.DbStatements;
|
||||
import io.helidon.dbclient.common.InterceptorSupport;
|
||||
import io.helidon.dbclient.spi.DbClientProviderBuilder;
|
||||
import io.helidon.dbclient.spi.DbMapperProvider;
|
||||
|
||||
/**
|
||||
* Fluent API builder for {@link JdbcDbClientProviderBuilder} that implements
|
||||
* the {@link io.helidon.dbclient.spi.DbClientProviderBuilder} from Helidon DB API.
|
||||
*/
|
||||
public final class JdbcDbClientProviderBuilder implements DbClientProviderBuilder<JdbcDbClientProviderBuilder> {
|
||||
|
||||
private final InterceptorSupport.Builder interceptors = InterceptorSupport.builder();
|
||||
private final DbMapperManager.Builder dbMapperBuilder = DbMapperManager.builder();
|
||||
|
||||
private String url;
|
||||
private String username;
|
||||
private String password;
|
||||
private DbStatements statements;
|
||||
private MapperManager mapperManager;
|
||||
private DbMapperManager dbMapperManager;
|
||||
private Supplier<ExecutorService> executorService;
|
||||
private ConnectionPool connectionPool;
|
||||
|
||||
JdbcDbClientProviderBuilder() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbClient build() {
|
||||
if (null == connectionPool) {
|
||||
if (null == url) {
|
||||
throw new DbClientException("No database connection configuration (%s) was found. Use \"connection\" "
|
||||
+ "configuration key, or configure on builder using \"connectionPool"
|
||||
+ "(ConnectionPool)\"");
|
||||
}
|
||||
connectionPool = ConnectionPool.builder()
|
||||
.url(url)
|
||||
.username(username)
|
||||
.password(password)
|
||||
.build();
|
||||
}
|
||||
if (null == dbMapperManager) {
|
||||
this.dbMapperManager = dbMapperBuilder.build();
|
||||
}
|
||||
if (null == mapperManager) {
|
||||
this.mapperManager = MapperManager.create();
|
||||
}
|
||||
if (null == executorService) {
|
||||
executorService = ThreadPoolSupplier.create();
|
||||
}
|
||||
return new JdbcDbClient(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcDbClientProviderBuilder config(Config config) {
|
||||
config.get("connection")
|
||||
.detach()
|
||||
.ifExists(cfg -> connectionPool(ConnectionPool.create(cfg)));
|
||||
|
||||
config.get("statements").as(DbStatements::create).ifPresent(this::statements);
|
||||
config.get("executor-service").as(ThreadPoolSupplier::create).ifPresent(this::executorService);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a connection pool.
|
||||
*
|
||||
* @param connectionPool connection pool to get connections to a database
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public JdbcDbClientProviderBuilder connectionPool(ConnectionPool connectionPool) {
|
||||
this.connectionPool = connectionPool;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure an explicit executor service supplier.
|
||||
* The executor service is used to execute blocking calls to a database.
|
||||
*
|
||||
* @param executorServiceSupplier supplier to obtain an executor service from
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public JdbcDbClientProviderBuilder executorService(Supplier<ExecutorService> executorServiceSupplier) {
|
||||
this.executorService = executorServiceSupplier;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcDbClientProviderBuilder url(String url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcDbClientProviderBuilder username(String username) {
|
||||
this.username = username;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcDbClientProviderBuilder password(String password) {
|
||||
this.password = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcDbClientProviderBuilder statements(DbStatements statements) {
|
||||
this.statements = statements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <TYPE> JdbcDbClientProviderBuilder addMapper(DbMapper<TYPE> dbMapper, Class<TYPE> mappedClass) {
|
||||
this.dbMapperBuilder.addMapperProvider(new DbMapperProvider() {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> Optional<DbMapper<T>> mapper(Class<T> type) {
|
||||
if (type.equals(mappedClass)) {
|
||||
return Optional.of((DbMapper<T>) dbMapper);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <TYPE> JdbcDbClientProviderBuilder addMapper(DbMapper<TYPE> dbMapper, GenericType<TYPE> mappedType) {
|
||||
this.dbMapperBuilder.addMapperProvider(new DbMapperProvider() {
|
||||
@Override
|
||||
public <T> Optional<DbMapper<T>> mapper(Class<T> type) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> Optional<DbMapper<T>> mapper(GenericType<T> type) {
|
||||
if (type.equals(mappedType)) {
|
||||
return Optional.of((DbMapper<T>) dbMapper);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcDbClientProviderBuilder mapperManager(MapperManager manager) {
|
||||
this.mapperManager = manager;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcDbClientProviderBuilder addMapperProvider(DbMapperProvider provider) {
|
||||
this.dbMapperBuilder.addMapperProvider(provider);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcDbClientProviderBuilder addInterceptor(DbInterceptor interceptor) {
|
||||
this.interceptors.add(interceptor);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcDbClientProviderBuilder addInterceptor(DbInterceptor interceptor, String... statementNames) {
|
||||
this.interceptors.add(interceptor, statementNames);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcDbClientProviderBuilder addInterceptor(DbInterceptor interceptor, DbStatementType... dbStatementTypes) {
|
||||
this.interceptors.add(interceptor, dbStatementTypes);
|
||||
return this;
|
||||
}
|
||||
|
||||
DbStatements statements() {
|
||||
return statements;
|
||||
}
|
||||
|
||||
InterceptorSupport interceptors() {
|
||||
return interceptors.build();
|
||||
}
|
||||
|
||||
DbMapperManager dbMapperManager() {
|
||||
return dbMapperManager;
|
||||
}
|
||||
|
||||
MapperManager mapperManager() {
|
||||
return mapperManager;
|
||||
}
|
||||
|
||||
ExecutorService executorService() {
|
||||
return executorService.get();
|
||||
}
|
||||
|
||||
ConnectionPool connectionPool() {
|
||||
return connectionPool;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import io.helidon.common.mapper.MapperManager;
|
||||
import io.helidon.dbclient.DbMapperManager;
|
||||
import io.helidon.dbclient.common.InterceptorSupport;
|
||||
|
||||
/**
|
||||
* Stuff needed by each and every statement.
|
||||
*/
|
||||
final class JdbcExecuteContext {
|
||||
|
||||
private final ExecutorService executorService;
|
||||
private final InterceptorSupport interceptors;
|
||||
private final DbMapperManager dbMapperManager;
|
||||
private final MapperManager mapperManager;
|
||||
private final String dbType;
|
||||
private final CompletionStage<Connection> connection;
|
||||
private final ConcurrentHashMap.KeySetView<CompletableFuture<Long>, Boolean> futures = ConcurrentHashMap.newKeySet();
|
||||
|
||||
private JdbcExecuteContext(ExecutorService executorService,
|
||||
InterceptorSupport interceptors,
|
||||
DbMapperManager dbMapperManager,
|
||||
MapperManager mapperManager,
|
||||
String dbType,
|
||||
CompletionStage<Connection> connection) {
|
||||
this.executorService = executorService;
|
||||
this.interceptors = interceptors;
|
||||
this.dbMapperManager = dbMapperManager;
|
||||
this.mapperManager = mapperManager;
|
||||
this.dbType = dbType;
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
static JdbcExecuteContext create(ExecutorService executorService,
|
||||
InterceptorSupport interceptors,
|
||||
String dbType,
|
||||
CompletionStage<Connection> connection,
|
||||
DbMapperManager dbMapperManager,
|
||||
MapperManager mapperManager) {
|
||||
return new JdbcExecuteContext(executorService,
|
||||
interceptors,
|
||||
dbMapperManager,
|
||||
mapperManager,
|
||||
dbType,
|
||||
connection);
|
||||
}
|
||||
|
||||
ExecutorService executorService() {
|
||||
return executorService;
|
||||
}
|
||||
|
||||
InterceptorSupport interceptors() {
|
||||
return interceptors;
|
||||
}
|
||||
|
||||
DbMapperManager dbMapperManager() {
|
||||
return dbMapperManager;
|
||||
}
|
||||
|
||||
MapperManager mapperManager() {
|
||||
return mapperManager;
|
||||
}
|
||||
|
||||
String dbType() {
|
||||
return dbType;
|
||||
}
|
||||
|
||||
CompletionStage<Connection> connection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
void addFuture(CompletableFuture<Long> queryFuture) {
|
||||
this.futures.add(queryFuture);
|
||||
}
|
||||
|
||||
public CompletionStage<Void> whenComplete() {
|
||||
CompletionStage<?> overallStage = CompletableFuture.completedFuture(null);
|
||||
|
||||
for (CompletableFuture<Long> future : futures) {
|
||||
overallStage = overallStage.thenCompose(o -> future);
|
||||
}
|
||||
|
||||
return overallStage.thenAccept(it -> {
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.jdbc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* An executor that cycles through in-process queries and executes read operations.
|
||||
*/
|
||||
class JdbcQueryExecutor {
|
||||
|
||||
private final Random random = new Random();
|
||||
private final List<StmtRunnable> runnables = new ArrayList<>();
|
||||
|
||||
void submit(QueryProcessor processor) {
|
||||
/*
|
||||
The idea is to associate the processor with a thread that is next to have free cycles.
|
||||
The same thread should process the same processors - e.g. the tryNext should always read only a single record (or a
|
||||
small set of records) from the result set.
|
||||
The number of threads to use must be configurable (and may be changing over time such as in an executor service
|
||||
Once the query processor completes, we remove it from teh cycle of that thread
|
||||
*/
|
||||
for (StmtRunnable runnable : runnables) {
|
||||
if (runnable.processors.isEmpty()) {
|
||||
runnable.addProcessor(processor);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (StmtRunnable runnable : runnables) {
|
||||
if (runnable.idle.get()) {
|
||||
runnable.addProcessor(processor);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// none is idle, add it to a random one
|
||||
// TODO this must have size limits on the number of processors per runnable - what to do if all busy? add a thread
|
||||
// what to do if all threads are used - throw a nice exception
|
||||
// we rather refuse work than kill everything
|
||||
runnables.get(random.nextInt(runnables.size())).addProcessor(processor);
|
||||
}
|
||||
|
||||
interface QueryProcessor {
|
||||
boolean tryNext();
|
||||
|
||||
boolean isCompleted();
|
||||
}
|
||||
|
||||
// FIXME: This may need some review and redesign.
|
||||
private static class StmtRunnable implements Runnable {
|
||||
|
||||
private final Set<QueryProcessor> processors = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||
private final AtomicBoolean idle = new AtomicBoolean();
|
||||
private final AtomicBoolean enabled = new AtomicBoolean(true);
|
||||
|
||||
void addProcessor(QueryProcessor processor) {
|
||||
// lock
|
||||
processors.add(processor);
|
||||
// unlock
|
||||
// we have added a processor, maybe it wants to do stuff immediately
|
||||
requestRun();
|
||||
}
|
||||
|
||||
void requestRun() {
|
||||
// let the next run know, that it should run, or release the waiting "run" method
|
||||
// something.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (enabled.get()) {
|
||||
// this is the idle loop
|
||||
idle.set(true);
|
||||
// something.await();
|
||||
idle.set(false);
|
||||
|
||||
// this is the one-cycle of processing loop
|
||||
boolean working = true;
|
||||
while (working) {
|
||||
working = false;
|
||||
|
||||
List<QueryProcessor> toRemove = new LinkedList<>();
|
||||
// read lock
|
||||
for (QueryProcessor processor : processors) {
|
||||
if (processor.isCompleted()) {
|
||||
toRemove.add(processor);
|
||||
} else {
|
||||
if (processor.tryNext()) {
|
||||
working = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// write lock
|
||||
toRemove.forEach(processors::remove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,748 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import io.helidon.dbclient.DbClientException;
|
||||
import io.helidon.dbclient.DbInterceptorContext;
|
||||
import io.helidon.dbclient.DbStatement;
|
||||
import io.helidon.dbclient.common.AbstractStatement;
|
||||
|
||||
/**
|
||||
* Common JDBC statement builder.
|
||||
*
|
||||
* @param <S> subclass of this class
|
||||
* @param <R> Statement execution result type
|
||||
*/
|
||||
abstract class JdbcStatement<S extends DbStatement<S, R>, R> extends AbstractStatement<S, R> {
|
||||
|
||||
/** Local logger instance. */
|
||||
private static final Logger LOGGER = Logger.getLogger(JdbcStatement.class.getName());
|
||||
|
||||
private final ExecutorService executorService;
|
||||
private final String dbType;
|
||||
private final CompletionStage<Connection> connection;
|
||||
private final JdbcExecuteContext executeContext;
|
||||
|
||||
JdbcStatement(JdbcExecuteContext executeContext, JdbcStatementContext statementContext) {
|
||||
super(statementContext.statementType(),
|
||||
statementContext.statementName(),
|
||||
statementContext.statement(),
|
||||
executeContext.dbMapperManager(),
|
||||
executeContext.mapperManager(),
|
||||
executeContext.interceptors());
|
||||
|
||||
this.executeContext = executeContext;
|
||||
this.dbType = executeContext.dbType();
|
||||
this.connection = executeContext.connection();
|
||||
this.executorService = executeContext.executorService();
|
||||
}
|
||||
|
||||
PreparedStatement build(Connection conn, DbInterceptorContext dbContext) {
|
||||
LOGGER.fine(() -> String.format("Building SQL statement: %s", dbContext.statement()));
|
||||
String statement = dbContext.statement();
|
||||
String statementName = dbContext.statementName();
|
||||
|
||||
Supplier<PreparedStatement> simpleStatementSupplier = () -> prepareStatement(conn, statementName, statement);
|
||||
|
||||
if (dbContext.isIndexed()) {
|
||||
return dbContext.indexedParameters()
|
||||
.map(params -> prepareIndexedStatement(conn, statementName, statement, params))
|
||||
.orElseGet(simpleStatementSupplier);
|
||||
} else {
|
||||
return dbContext.namedParameters()
|
||||
.map(params -> prepareNamedStatement(conn, statementName, statement, params))
|
||||
.orElseGet(simpleStatementSupplier);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to {@link #build(java.sql.Connection, io.helidon.dbclient.DbInterceptorContext)} and use interceptors.
|
||||
*
|
||||
* @param connection connection to use
|
||||
* @return prepared statement
|
||||
*/
|
||||
@Deprecated
|
||||
protected PreparedStatement build(Connection connection) {
|
||||
LOGGER.fine(() -> String.format("Building SQL statement: %s", statement()));
|
||||
switch (paramType()) {
|
||||
// Statement may not contain any parameters, no conversion is needed.
|
||||
case UNKNOWN:
|
||||
return prepareStatement(connection, statementName(), statement());
|
||||
case INDEXED:
|
||||
return prepareIndexedStatement(connection, statementName(), statement(), indexedParams());
|
||||
case NAMED:
|
||||
return prepareNamedStatement(connection, statementName(), statement(), namedParams());
|
||||
default:
|
||||
throw new IllegalStateException("Unknown SQL statement type");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String dbType() {
|
||||
return dbType;
|
||||
}
|
||||
|
||||
CompletionStage<Connection> connection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
ExecutorService executorService() {
|
||||
return executorService;
|
||||
}
|
||||
|
||||
JdbcExecuteContext executeContext() {
|
||||
return executeContext;
|
||||
}
|
||||
|
||||
private PreparedStatement prepareStatement(Connection conn, String statementName, String statement) {
|
||||
try {
|
||||
return conn.prepareStatement(statement);
|
||||
} catch (SQLException e) {
|
||||
throw new DbClientException(String.format("Failed to prepare statement: %s", statementName), e);
|
||||
}
|
||||
}
|
||||
|
||||
private PreparedStatement prepareNamedStatement(Connection connection,
|
||||
String statementName,
|
||||
String statement,
|
||||
Map<String, Object> parameters) {
|
||||
|
||||
PreparedStatement preparedStatement = null;
|
||||
try {
|
||||
// Parameters names must be replaced with ? and names occurence order must be stored.
|
||||
Parser parser = new Parser(statement);
|
||||
String jdbcStatement = parser.convert();
|
||||
LOGGER.finest(() -> String.format("Converted statement: %s", jdbcStatement));
|
||||
preparedStatement = connection.prepareStatement(jdbcStatement);
|
||||
List<String> namesOrder = parser.namesOrder();
|
||||
// SQL statement and provided parameters integrity check
|
||||
if (namesOrder.size() > parameters.size()) {
|
||||
throw new DbClientException(namedStatementErrorMessage(namesOrder, parameters));
|
||||
}
|
||||
// Set parameters into prepared statement
|
||||
int i = 1;
|
||||
for (String name : namesOrder) {
|
||||
if (parameters.containsKey(name)) {
|
||||
Object value = parameters.get(name);
|
||||
LOGGER.finest(String.format("Mapped parameter %d: %s -> %s", i, name, value));
|
||||
preparedStatement.setObject(i, value);
|
||||
i++;
|
||||
} else {
|
||||
throw new DbClientException(namedStatementErrorMessage(namesOrder, parameters));
|
||||
}
|
||||
}
|
||||
return preparedStatement;
|
||||
} catch (SQLException e) {
|
||||
closePreparedStatement(preparedStatement);
|
||||
throw new DbClientException("Failed to prepare statement with named parameters: " + statementName, e);
|
||||
}
|
||||
}
|
||||
|
||||
private PreparedStatement prepareIndexedStatement(Connection connection,
|
||||
String statementName,
|
||||
String statement,
|
||||
List<Object> parameters) {
|
||||
|
||||
PreparedStatement preparedStatement = null;
|
||||
try {
|
||||
preparedStatement = connection.prepareStatement(statement);
|
||||
int i = 1; // JDBC set position parameter starts from 1.
|
||||
for (Object value : parameters) {
|
||||
LOGGER.finest(String.format("Indexed parameter %d: %s", i, value));
|
||||
preparedStatement.setObject(i, value);
|
||||
// increase value for next iteration
|
||||
i++;
|
||||
}
|
||||
return preparedStatement;
|
||||
} catch (SQLException e) {
|
||||
closePreparedStatement(preparedStatement);
|
||||
throw new DbClientException(String.format("Failed to prepare statement with indexed params: %s", statementName), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void closePreparedStatement(final PreparedStatement preparedStatement) {
|
||||
if (preparedStatement != null) {
|
||||
try {
|
||||
preparedStatement.close();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.log(Level.WARNING, String.format("Could not close PreparedStatement: %s", e.getMessage()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String namedStatementErrorMessage(final List<String> namesOrder, final Map<String, Object> parameters) {
|
||||
// Parameters in query missing in parameters Map
|
||||
List<String> notInParams = new ArrayList<>(namesOrder.size());
|
||||
for (String name : namesOrder) {
|
||||
if (!parameters.containsKey(name)) {
|
||||
notInParams.add(name);
|
||||
}
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Query parameters missing in Map: ");
|
||||
boolean first = true;
|
||||
for (String name : notInParams) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(name);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping parser state machine.
|
||||
*
|
||||
* Before replacement:
|
||||
* {@code SELECT * FROM table WHERE name = :name AND type = :type}
|
||||
* After replacement:
|
||||
* {@code SELECT * FROM table WHERE name = ? AND type = ?}
|
||||
* Expected list of parameters:
|
||||
* {@code "name", "type"}
|
||||
*/
|
||||
static final class Parser {
|
||||
@FunctionalInterface
|
||||
|
||||
private interface Action extends Consumer<Parser> {}
|
||||
|
||||
/**
|
||||
* Character classes used in state machine.
|
||||
*/
|
||||
private enum CharClass {
|
||||
LETTER, // Letter (any unicode letter)
|
||||
NUMBER, // Number (any unicode digit)
|
||||
LF, // Line feed / new line (\n), terminates line alone or in CR LF sequence
|
||||
CR, // Carriage return (\r), terminates line in CR LF sequence
|
||||
APOSTROPHE, // Single quote ('), begins string in SQL
|
||||
STAR, // Star (*), part of multiline comment beginning "/*" and ending "*/" sequence
|
||||
DASH, // Dash (-), part of single line comment beginning sequence "--"
|
||||
SLASH, // Slash (/), part of multiline comment beginning "/*" and ending "*/" sequence
|
||||
COLON, // Colon (:), begins named parameter
|
||||
OTHER; // Other characters
|
||||
|
||||
/**
|
||||
* Returns character class corresponding to provided character.
|
||||
*
|
||||
* @param c character to determine its character class
|
||||
* @return character class corresponding to provided character
|
||||
*/
|
||||
private static CharClass charClass(char c) {
|
||||
switch (c) {
|
||||
case '\r': return CR;
|
||||
case '\n': return LF;
|
||||
case '\'': return APOSTROPHE;
|
||||
case '*': return STAR;
|
||||
case '-': return DASH;
|
||||
case '/': return SLASH;
|
||||
case ':': return COLON;
|
||||
default:
|
||||
return Character.isLetter(c)
|
||||
? LETTER
|
||||
: (Character.isDigit(c) ? NUMBER : OTHER);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* States used in state machine.
|
||||
*/
|
||||
private enum State {
|
||||
STATEMENT, // Common statement processing
|
||||
STRING, // SQL string processing after 1st APOSTROPHE was recieved
|
||||
COLON, // Symbolic name processing after opening COLON (colon) was recieved
|
||||
PARAMETER, // Symbolic name processing after 1st LETTER or later LETTER
|
||||
// or NUMBER of parameter name was recieved
|
||||
MULTILN_COMMENT_BG, // Multiline comment processing after opening slash was recieved from the "/*" sequence
|
||||
MULTILN_COMMENT_END, // Multiline comment processing after closing star was recieved from the "*/" sequence
|
||||
MULTILN_COMMENT, // Multiline comment processing of the comment itself
|
||||
SINGLELN_COMMENT_BG, // Single line comment processing after opening dash was recieved from the "--" sequence
|
||||
SINGLELN_COMMENT_END, // Single line comment processing after closing CR was recieved from the CR LF sequence
|
||||
SINGLELN_COMMENT; // Single line comment processing of the comment itself
|
||||
|
||||
/** States transition table. */
|
||||
private static final State[][] TRANSITION = {
|
||||
// Transitions from STATEMENT state
|
||||
{
|
||||
STATEMENT, // LETTER: regular part of the statement, keep processing it
|
||||
STATEMENT, // NUMBER: regular part of the statement, keep processing it
|
||||
STATEMENT, // LF: regular part of the statement, keep processing it
|
||||
STATEMENT, // CR: regular part of the statement, keep processing it
|
||||
STRING, // APOSTROPHE: beginning of SQL string processing, switch to STRING state
|
||||
STATEMENT, // STAR: regular part of the statement, keep processing it
|
||||
SINGLELN_COMMENT_BG, // DASH: possible starting sequence of single line comment,
|
||||
// switch to SINGLELN_COMMENT_BG state
|
||||
MULTILN_COMMENT_BG, // SLASH: possible starting sequence of multi line comment,
|
||||
// switch to MULTILN_COMMENT_BG state
|
||||
COLON, // COLON: possible beginning of named parameter, switch to COLON state
|
||||
STATEMENT // OTHER: regular part of the statement, keep processing it
|
||||
},
|
||||
// Transitions from STRING state
|
||||
{
|
||||
STRING, // LETTER: regular part of the SQL string, keep processing it
|
||||
STRING, // NUMBER: regular part of the SQL string, keep processing it
|
||||
STRING, // LF: regular part of the SQL string, keep processing it
|
||||
STRING, // CR: regular part of the SQL string, keep processing it
|
||||
STATEMENT, // APOSTROPHE: end of SQL string processing, go back to STATEMENT state
|
||||
STRING, // STAR: regular part of the SQL string, keep processing it
|
||||
STRING, // DASH: regular part of the SQL string, keep processing it
|
||||
STRING, // SLASH: regular part of the SQL string, keep processing it
|
||||
STRING, // COLON: regular part of the SQL string, keep processing it
|
||||
STRING // OTHER: regular part of the SQL string, keep processing it
|
||||
},
|
||||
// Transitions from COLON state
|
||||
{
|
||||
PARAMETER, // LETTER: first character of named parameter, switch to PARAMETER state
|
||||
STATEMENT, // NUMBER: can't be first character of named parameter, go back to STATEMENT state
|
||||
STATEMENT, // LF: can't be first character of named parameter, go back to STATEMENT state
|
||||
STATEMENT, // CR: can't be first character of named parameter, go back to STATEMENT state
|
||||
STRING, // APOSTROPHE: not a named parameter but beginning of SQL string processing,
|
||||
// switch to STRING state
|
||||
STATEMENT, // STAR: can't be first character of named parameter, go back to STATEMENT state
|
||||
SINGLELN_COMMENT_BG, // DASH: not a named parameter but possible starting sequence of single line comment,
|
||||
// switch to SINGLELN_COMMENT_BG state
|
||||
MULTILN_COMMENT_BG, // SLASH: not a named parameter but possible starting sequence of multi line comment,
|
||||
// switch to MULTILN_COMMENT_BG state
|
||||
COLON, // COLON: not a named parameter but possible beginning of another named parameter,
|
||||
// retry named parameter processing
|
||||
STATEMENT // OTHER: can't be first character of named parameter, go back to STATEMENT state
|
||||
},
|
||||
// Transitions from PARAMETER state
|
||||
{
|
||||
PARAMETER, // LETTER: next character of named parameter, keep processing it
|
||||
PARAMETER, // NUMBER: next character of named parameter, keep processing it
|
||||
STATEMENT, // LF: can't be next character of named parameter, go back to STATEMENT state
|
||||
STATEMENT, // CR: can't be next character of named parameter, go back to STATEMENT state
|
||||
STRING, // APOSTROPHE: end of named parameter and beginning of SQL string processing,
|
||||
// switch to STRING state
|
||||
STATEMENT, // STAR: can't be next character of named parameter, go back to STATEMENT state
|
||||
SINGLELN_COMMENT_BG, // DASH: end of named parameter and possible starting sequence of single line comment,
|
||||
// switch to SINGLELN_COMMENT_BG state
|
||||
MULTILN_COMMENT_BG, // SLASH: end of named parameter and possible starting sequence of multi line comment,
|
||||
// switch to MULTILN_COMMENT_BG state
|
||||
COLON, // COLON: end of named parameter and possible beginning of another named parameter,
|
||||
// switch to COLON state to restart named parameter processing
|
||||
STATEMENT // OTHER: can't be next character of named parameter, go back to STATEMENT state
|
||||
},
|
||||
// Transitions from MULTILN_COMMENT_BG state
|
||||
{
|
||||
STATEMENT, // LETTER: not starting sequence of multi line comment, go back to STATEMENT state
|
||||
STATEMENT, // NUMBER: not starting sequence of multi line comment, go back to STATEMENT state
|
||||
STATEMENT, // LF: not starting sequence of multi line comment, go back to STATEMENT state
|
||||
STATEMENT, // CR: not starting sequence of multi line comment, go back to STATEMENT state
|
||||
STRING, // APOSTROPHE: not starting sequence of multi line comment but beginning of SQL
|
||||
// string processing, switch to STRING state
|
||||
MULTILN_COMMENT, // STAR: end of starting sequence of multi line comment,
|
||||
// switch to MULTILN_COMMENT state
|
||||
SINGLELN_COMMENT_BG, // DASH: not starting sequence of multi line comment but possible starting sequence
|
||||
// of single line comment, switch to SINGLELN_COMMENT_BG state
|
||||
MULTILN_COMMENT_BG, // SLASH: not starting sequence of multi line comment but possible starting sequence
|
||||
// of next multi line comment, retry multi line comment processing
|
||||
COLON, // COLON: not starting sequence of multi line comment but possible beginning
|
||||
// of named parameter, switch to COLON state
|
||||
STATEMENT // OTHER: not starting sequence of multi line comment, go back to STATEMENT state
|
||||
},
|
||||
// Transitions from MULTILN_COMMENT_END state
|
||||
{
|
||||
MULTILN_COMMENT, // LETTER: not ending sequence of multi line comment, go back to MULTILN_COMMENT state
|
||||
MULTILN_COMMENT, // NUMBER: not ending sequence of multi line comment, go back to MULTILN_COMMENT state
|
||||
MULTILN_COMMENT, // LF: not ending sequence of multi line comment, go back to MULTILN_COMMENT state
|
||||
MULTILN_COMMENT, // CR: not ending sequence of multi line comment, go back to MULTILN_COMMENT state
|
||||
MULTILN_COMMENT, // APOSTROPHE: not ending sequence of multi line comment,
|
||||
// go back to MULTILN_COMMENT state
|
||||
MULTILN_COMMENT_END, // STAR: not ending sequence of multi line comment but possible ending sequence
|
||||
// of next multi line comment, retry end of multi line comment processing
|
||||
MULTILN_COMMENT, // DASH: not ending sequence of multi line comment, go back to MULTILN_COMMENT state
|
||||
STATEMENT, // SLASH: end of ending sequence of multi line comment,
|
||||
// switch to STATEMENT state
|
||||
MULTILN_COMMENT, // COLON: not ending sequence of multi line comment, go back to MULTILN_COMMENT state
|
||||
MULTILN_COMMENT // OTHER: not ending sequence of multi line comment, go back to MULTILN_COMMENT state
|
||||
},
|
||||
// Transitions from MULTILN_COMMENT state
|
||||
{
|
||||
MULTILN_COMMENT, // LETTER: regular multi line comment, keep processing it
|
||||
MULTILN_COMMENT, // NUMBER: regular multi line comment, keep processing it
|
||||
MULTILN_COMMENT, // LF: regular multi line comment, keep processing it
|
||||
MULTILN_COMMENT, // CR: regular multi line comment, keep processing it
|
||||
MULTILN_COMMENT, // APOSTROPHE: regular multi line comment, keep processing it
|
||||
MULTILN_COMMENT_END, // STAR: possible ending sequence of multi line comment,
|
||||
// switch to MULTILN_COMMENT_END state
|
||||
MULTILN_COMMENT, // DASH: regular multi line comment, keep processing it
|
||||
MULTILN_COMMENT, // SLASH: regular multi line comment, keep processing it
|
||||
MULTILN_COMMENT, // COLON: regular multi line comment, keep processing it
|
||||
MULTILN_COMMENT // OTHER: regular multi line comment, keep processing it
|
||||
},
|
||||
// Transitions from SINGLELN_COMMENT_BG state
|
||||
{
|
||||
STATEMENT, // LETTER: not starting sequence of single line comment, go back to STATEMENT state
|
||||
STATEMENT, // NUMBER: not starting sequence of single line comment, go back to STATEMENT state
|
||||
STATEMENT, // LF: not starting sequence of single line comment, go back to STATEMENT state
|
||||
STATEMENT, // CR: not starting sequence of single line comment, go back to STATEMENT state
|
||||
STRING, // APOSTROPHE: not starting sequence of single line comment but beginning of SQL
|
||||
// string processing, switch to STRING state
|
||||
STATEMENT, // STAR: not starting sequence of single line comment, go back to STATEMENT state
|
||||
SINGLELN_COMMENT, // DASH: end of starting sequence of single line comment,
|
||||
// switch to SINGLELN_COMMENT state
|
||||
MULTILN_COMMENT_BG, // SLASH: not starting sequence of single line comment but possible starting sequence
|
||||
// of next multi line comment, switch to MULTILN_COMMENT_BG state
|
||||
COLON, // COLON: not starting sequence of single line comment but possible beginning
|
||||
// of named parameter, switch to COLON state
|
||||
STATEMENT // OTHER: not starting sequence of single line comment, go back to STATEMENT state
|
||||
},
|
||||
// Transitions from SINGLELN_COMMENT_END state
|
||||
{
|
||||
SINGLELN_COMMENT, // LETTER: not ending sequence of single line comment, go back to SINGLELN_COMMENT state
|
||||
SINGLELN_COMMENT, // NUMBER: not ending sequence of single line comment, go back to SINGLELN_COMMENT state
|
||||
STATEMENT, // LF: end of single line comment, switch to STATEMENT state
|
||||
SINGLELN_COMMENT_END, // CR: not ending sequence of single line comment but possible ending sequence
|
||||
// of next single line comment, retry end of single line comment processing
|
||||
SINGLELN_COMMENT, // APOSTROPHE: not ending sequence of single line comment,
|
||||
// go back to SINGLELN_COMMENT state
|
||||
SINGLELN_COMMENT, // STAR: not ending sequence of single line comment, go back to SINGLELN_COMMENT state
|
||||
SINGLELN_COMMENT, // DASH: not ending sequence of single line comment, go back to SINGLELN_COMMENT state
|
||||
SINGLELN_COMMENT, // SLASH: not ending sequence of single line comment, go back to SINGLELN_COMMENT state
|
||||
SINGLELN_COMMENT, // COLON: not ending sequence of single line comment, go back to SINGLELN_COMMENT state
|
||||
SINGLELN_COMMENT // OTHER: not ending sequence of single line comment, go back to SINGLELN_COMMENT state
|
||||
},
|
||||
// Transitions from SINGLELN_COMMENT state
|
||||
{
|
||||
SINGLELN_COMMENT, // LETTER: regular single line comment, keep processing it
|
||||
SINGLELN_COMMENT, // NUMBER: regular single line comment, keep processing it
|
||||
STATEMENT, // LF: end of single line comment, switch to STATEMENT state
|
||||
SINGLELN_COMMENT_END, // CR: possible beginning of ending sequence of multi line comment,
|
||||
// switch to SINGLELN_COMMENT_END state
|
||||
SINGLELN_COMMENT, // APOSTROPHE: regular single line comment, keep processing it
|
||||
SINGLELN_COMMENT, // STAR: regular single line comment, keep processing it
|
||||
SINGLELN_COMMENT, // DASH: regular single line comment, keep processing it
|
||||
SINGLELN_COMMENT, // SLASH: regular single line comment, keep processing it
|
||||
SINGLELN_COMMENT, // COLON: regular single line comment, keep processing it
|
||||
SINGLELN_COMMENT // OTHER: regular single line comment, keep processing it
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* State automaton action table.
|
||||
*/
|
||||
private static final Action[][] ACTION = {
|
||||
// Actions performed on transitions from STATEMENT state
|
||||
{
|
||||
Parser::copyChar, // LETTER: copy regular statement character to output
|
||||
Parser::copyChar, // NUMBER: copy regular statement character to output
|
||||
Parser::copyChar, // LF: copy regular statement character to output
|
||||
Parser::copyChar, // CR: copy regular statement character to output
|
||||
Parser::copyChar, // APOSTROPHE: copy SQL string character to output
|
||||
Parser::copyChar, // STAR: copy regular statement character to output
|
||||
Parser::copyChar, // DASH: copy character to output, no matter wheter it's comment or not
|
||||
Parser::copyChar, // SLASH: copy character to output, no matter wheter it's comment or not
|
||||
Parser::doNothing, // COLON: delay character copying until it's obvious whether this is parameter or not
|
||||
Parser::copyChar // OTHER: copy regular statement character to output
|
||||
},
|
||||
// Actions performed on transitions from STRING state
|
||||
{
|
||||
Parser::copyChar, // LETTER: copy SQL string character to output
|
||||
Parser::copyChar, // NUMBER: copy SQL string character to output
|
||||
Parser::copyChar, // LF: copy SQL string character to output
|
||||
Parser::copyChar, // CR: copy SQL string character to output
|
||||
Parser::copyChar, // APOSTROPHE: copy SQL string character to output
|
||||
Parser::copyChar, // STAR: copy SQL string character to output
|
||||
Parser::copyChar, // DASH: copy SQL string character to output
|
||||
Parser::copyChar, // SLASH: copy SQL string character to output
|
||||
Parser::copyChar, // COLON: copy SQL string character to output
|
||||
Parser::copyChar // OTHER: copy SQL string character to output
|
||||
},
|
||||
// Actions performed on transitions from COLON state
|
||||
{
|
||||
Parser::setFirstParamChar, // LETTER: set first parameter character
|
||||
Parser::addColonAndCopyChar, // NUMBER: not a parameter, add delayed colon and copy current statement character
|
||||
// to output
|
||||
Parser::addColonAndCopyChar, // LF: not a parameter, add delayed colon and copy current statement character
|
||||
// to output
|
||||
Parser::addColonAndCopyChar, // CR: not a parameter, add delayed colon and copy current statement character
|
||||
// to output
|
||||
Parser::addColonAndCopyChar, // APOSTROPHE: not a parameter, add delayed colon and copy current SQL string
|
||||
// character to output
|
||||
Parser::addColonAndCopyChar, // STAR: not a parameter, add delayed colon and copy current statement character
|
||||
// to output
|
||||
Parser::addColonAndCopyChar, // DASH: not a parameter, add delayed colon and copy current statement character
|
||||
// to output, no matter wheter it's comment or not
|
||||
Parser::addColonAndCopyChar, // SLASH: not a parameter, add delayed colon and copy current statement character
|
||||
// to output, no matter wheter it's comment or not
|
||||
Parser::addColon, // COLON: not a parameter, add delayed colon and delay current colon copying
|
||||
// until it's obvious whether this is parameter or not
|
||||
Parser::addColonAndCopyChar // OTHER: not a parameter, add delayed colon and copy current statement character
|
||||
// to output
|
||||
},
|
||||
// Actions performed on transitions from PARAMETER state
|
||||
{
|
||||
Parser::setNextParamChar, // LETTER: set next parameter character
|
||||
Parser::setNextParamChar, // NUMBER: set next parameter character
|
||||
Parser::finishParamAndCopyChar, // LF: finish parameter processing and copy current character as part
|
||||
// of regular statement
|
||||
Parser::finishParamAndCopyChar, // CR: finish parameter processing and copy current character as part
|
||||
// of regular statement
|
||||
Parser::finishParamAndCopyChar, // APOSTROPHE: finish parameter processing and copy current character as part
|
||||
// of regular statement
|
||||
Parser::finishParamAndCopyChar, // STAR: finish parameter processing and copy current character as part
|
||||
// of regular statement
|
||||
Parser::finishParamAndCopyChar, // DASH: finish parameter processing and copy current character as part
|
||||
// of regular statement
|
||||
Parser::finishParamAndCopyChar, // SLASH: finish parameter processing and copy current character as part
|
||||
// of regular statement
|
||||
Parser::finishParam, // COLON: finish parameter processing and delay character copying until
|
||||
// it's obvious whether this is next parameter or not
|
||||
Parser::finishParamAndCopyChar // OTHER: finish parameter processing and copy current character as part
|
||||
// of regular statement
|
||||
},
|
||||
// Actions performed on transitions from MULTILN_COMMENT_BG state
|
||||
{
|
||||
Parser::copyChar, // LETTER: copy regular statement character to output
|
||||
Parser::copyChar, // NUMBER: copy regular statement character to output
|
||||
Parser::copyChar, // LF: copy regular statement character to output
|
||||
Parser::copyChar, // CR: copy regular statement character to output
|
||||
Parser::copyChar, // APOSTROPHE: copy SQL string character to output
|
||||
Parser::copyChar, // STAR: copy multi line comment character to output
|
||||
Parser::copyChar, // DASH: copy character to output, no matter wheter it's comment or not
|
||||
Parser::copyChar, // SLASH: copy character to output, no matter wheter it's comment or not
|
||||
Parser::doNothing, // COLON: delay character copying until it's obvious whether this is parameter or not
|
||||
Parser::copyChar // OTHER: copy regular statement character to output
|
||||
},
|
||||
// Actions performed on transitions from MULTILN_COMMENT_END state
|
||||
{
|
||||
Parser::copyChar, // LETTER: copy multi line comment character to output
|
||||
Parser::copyChar, // NUMBER: copy multi line comment character to output
|
||||
Parser::copyChar, // LF: copy multi line comment character to output
|
||||
Parser::copyChar, // CR: copy multi line comment character to output
|
||||
Parser::copyChar, // APOSTROPHE: copy multi line comment character to output
|
||||
Parser::copyChar, // STAR: copy multi line comment character to output
|
||||
Parser::copyChar, // DASH: copy multi line comment character to output
|
||||
Parser::copyChar, // SLASH: copy multi line comment character to output
|
||||
Parser::copyChar, // COLON: copy multi line comment character to output
|
||||
Parser::copyChar // OTHER: copy multi line comment character to output
|
||||
},
|
||||
// Actions performed on transitions from MULTILN_COMMENT state
|
||||
{
|
||||
Parser::copyChar, // LETTER: copy multi line comment character to output
|
||||
Parser::copyChar, // NUMBER: copy multi line comment character to output
|
||||
Parser::copyChar, // LF: copy multi line comment character to output
|
||||
Parser::copyChar, // CR: copy multi line comment character to output
|
||||
Parser::copyChar, // APOSTROPHE: copy multi line comment character to output
|
||||
Parser::copyChar, // STAR: copy multi line comment character to output
|
||||
Parser::copyChar, // DASH: copy multi line comment character to output
|
||||
Parser::copyChar, // SLASH: copy multi line comment character to output
|
||||
Parser::copyChar, // COLON: copy multi line comment character to output
|
||||
Parser::copyChar // OTHER: copy multi line comment character to output
|
||||
},
|
||||
// Actions performed on transitions from SINGLELN_COMMENT_BG state
|
||||
{
|
||||
Parser::copyChar, // LETTER: copy regular statement character to output
|
||||
Parser::copyChar, // NUMBER: copy regular statement character to output
|
||||
Parser::copyChar, // LF: copy regular statement character to output
|
||||
Parser::copyChar, // CR: copy regular statement character to output
|
||||
Parser::copyChar, // APOSTROPHE: copy SQL string character to output
|
||||
Parser::copyChar, // STAR: copy regular statement character to output
|
||||
Parser::copyChar, // DASH: copy single line comment character to output
|
||||
Parser::copyChar, // SLASH: copy character to output, no matter wheter it's comment or not
|
||||
Parser::doNothing, // COLON: delay character copying until it's obvious whether this is parameter or not
|
||||
Parser::copyChar // OTHER: copy regular statement character to output
|
||||
},
|
||||
// Actions performed on transitions from SINGLELN_COMMENT_END state
|
||||
{
|
||||
Parser::copyChar, // LETTER: copy single line comment character to output
|
||||
Parser::copyChar, // NUMBER: copy single line comment character to output
|
||||
Parser::copyChar, // LF: copy single line comment character to output
|
||||
Parser::copyChar, // CR: copy single line comment character to output
|
||||
Parser::copyChar, // APOSTROPHE: copy single line comment character to output
|
||||
Parser::copyChar, // STAR: copy single line comment character to output
|
||||
Parser::copyChar, // DASH: copy single line comment character to output
|
||||
Parser::copyChar, // SLASH: copy single line comment character to output
|
||||
Parser::copyChar, // COLON: copy single line comment character to output
|
||||
Parser::copyChar // OTHER: copy single line comment character to output
|
||||
},
|
||||
// Actions performed on transitions from SINGLELN_COMMENT state
|
||||
{
|
||||
Parser::copyChar, // LETTER: copy single line comment character to output
|
||||
Parser::copyChar, // NUMBER: copy single line comment character to output
|
||||
Parser::copyChar, // LF: copy single line comment character to output
|
||||
Parser::copyChar, // CR: copy single line comment character to output
|
||||
Parser::copyChar, // APOSTROPHE: copy single line comment character to output
|
||||
Parser::copyChar, // STAR: copy single line comment character to output
|
||||
Parser::copyChar, // DASH: copy single line comment character to output
|
||||
Parser::copyChar, // SLASH: copy single line comment character to output
|
||||
Parser::copyChar, // COLON: copy single line comment character to output
|
||||
Parser::copyChar // OTHER: copy single line comment character to output
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Do nothing.
|
||||
*
|
||||
* @param parser parser instance
|
||||
*/
|
||||
private static void doNothing(Parser parser) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy character from input string to output as is.
|
||||
*
|
||||
* @param parser parser instance
|
||||
*/
|
||||
private static void copyChar(Parser parser) {
|
||||
parser.sb.append(parser.c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add previous colon character to output.
|
||||
*
|
||||
* @param parser parser instance
|
||||
*/
|
||||
private static void addColon(Parser parser) {
|
||||
parser.sb.append(':');
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy previous colon and current input string character to output.
|
||||
*
|
||||
* @param parser parser instance
|
||||
*/
|
||||
private static void addColonAndCopyChar(Parser parser) {
|
||||
parser.sb.append(':');
|
||||
parser.sb.append(parser.c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store 1st named parameter letter.
|
||||
*
|
||||
* @param parser parser instance
|
||||
*/
|
||||
private static void setFirstParamChar(Parser parser) {
|
||||
parser.nap.setLength(0);
|
||||
parser.nap.append(parser.c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store next named parameter letter or number.
|
||||
*
|
||||
* @param parser parser instance
|
||||
*/
|
||||
private static void setNextParamChar(Parser parser) {
|
||||
parser.nap.append(parser.c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish stored named parameter and copy current character from input string to output as is.
|
||||
*
|
||||
* @param parser parser instance
|
||||
*/
|
||||
private static void finishParamAndCopyChar(Parser parser) {
|
||||
String parName = parser.nap.toString();
|
||||
parser.names.add(parName);
|
||||
parser.sb.append('?');
|
||||
parser.sb.append(parser.c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish stored named parameter without copying current character from input string to output as is.
|
||||
*
|
||||
* @param parser parser instance
|
||||
*/
|
||||
private static void finishParam(Parser parser) {
|
||||
String parName = parser.nap.toString();
|
||||
parser.names.add(parName);
|
||||
parser.sb.append('?');
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL statement to be parsed.
|
||||
*/
|
||||
private final String statement;
|
||||
|
||||
/**
|
||||
* Target SQL statement builder.
|
||||
*/
|
||||
private final StringBuilder sb;
|
||||
|
||||
/**
|
||||
* Temporary string storage.
|
||||
*/
|
||||
private final StringBuilder nap;
|
||||
|
||||
/**
|
||||
* Ordered list of parameter names.
|
||||
*/
|
||||
private final List<String> names;
|
||||
|
||||
/**
|
||||
* Character being currently processed.
|
||||
*/
|
||||
private char c;
|
||||
|
||||
/**
|
||||
* Character class of character being currently processed.
|
||||
*/
|
||||
private CharClass cl;
|
||||
|
||||
Parser(String statement) {
|
||||
this.sb = new StringBuilder(statement.length());
|
||||
this.nap = new StringBuilder(32);
|
||||
this.names = new LinkedList<>();
|
||||
this.statement = statement;
|
||||
this.c = '\0';
|
||||
this.cl = null;
|
||||
|
||||
}
|
||||
|
||||
String convert() {
|
||||
State state = State.STATEMENT; // Initial state: common statement processing
|
||||
int len = statement.length();
|
||||
for (int i = 0; i < len; i++) {
|
||||
c = statement.charAt(i);
|
||||
cl = CharClass.charClass(c);
|
||||
ACTION[state.ordinal()][cl.ordinal()].accept(this);
|
||||
state = State.TRANSITION[state.ordinal()][cl.ordinal()];
|
||||
}
|
||||
// Process end of statement
|
||||
if (state == State.PARAMETER) {
|
||||
String parName = nap.toString();
|
||||
names.add(parName);
|
||||
sb.append('?');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
List<String> namesOrder() {
|
||||
return names;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.jdbc;
|
||||
|
||||
import io.helidon.dbclient.DbStatementType;
|
||||
|
||||
/**
|
||||
* Stuff needed by each and every statement.
|
||||
*/
|
||||
class JdbcStatementContext {
|
||||
|
||||
private final DbStatementType statementType;
|
||||
private final String statementName;
|
||||
private final String statement;
|
||||
|
||||
private JdbcStatementContext(DbStatementType statementType, String statementName, String statement) {
|
||||
this.statementType = statementType;
|
||||
this.statementName = statementName;
|
||||
this.statement = statement;
|
||||
}
|
||||
|
||||
static JdbcStatementContext create(DbStatementType statementType, String statementName, String statement) {
|
||||
return new JdbcStatementContext(statementType, statementName, statement);
|
||||
}
|
||||
|
||||
DbStatementType statementType() {
|
||||
return statementType;
|
||||
}
|
||||
|
||||
String statementName() {
|
||||
return statementName;
|
||||
}
|
||||
|
||||
String statement() {
|
||||
return statement;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.jdbc;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
import io.helidon.dbclient.DbInterceptorContext;
|
||||
import io.helidon.dbclient.DbStatementDml;
|
||||
|
||||
class JdbcStatementDml extends JdbcStatement<DbStatementDml, Long> implements DbStatementDml {
|
||||
|
||||
JdbcStatementDml(JdbcExecuteContext executeContext,
|
||||
JdbcStatementContext statementContext) {
|
||||
super(executeContext, statementContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CompletionStage<Long> doExecute(CompletionStage<DbInterceptorContext> dbContextFuture,
|
||||
CompletableFuture<Void> statementFuture,
|
||||
CompletableFuture<Long> queryFuture) {
|
||||
|
||||
executeContext().addFuture(queryFuture);
|
||||
|
||||
// query and statement future must always complete either OK, or exceptionally
|
||||
dbContextFuture.exceptionally(throwable -> {
|
||||
statementFuture.completeExceptionally(throwable);
|
||||
queryFuture.completeExceptionally(throwable);
|
||||
return null;
|
||||
});
|
||||
|
||||
return dbContextFuture.thenCompose(dbContext -> {
|
||||
return connection().thenCompose(connection -> {
|
||||
executorService().submit(() -> {
|
||||
try {
|
||||
PreparedStatement preparedStatement = build(connection, dbContext);
|
||||
long count = preparedStatement.executeLargeUpdate();
|
||||
statementFuture.complete(null);
|
||||
queryFuture.complete(count);
|
||||
preparedStatement.close();
|
||||
} catch (Exception e) {
|
||||
statementFuture.completeExceptionally(e);
|
||||
queryFuture.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
// the query future is reused, as it completes with the number of updated records
|
||||
return queryFuture;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.jdbc;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import io.helidon.dbclient.DbInterceptorContext;
|
||||
import io.helidon.dbclient.DbResult;
|
||||
import io.helidon.dbclient.DbRow;
|
||||
import io.helidon.dbclient.DbRows;
|
||||
import io.helidon.dbclient.DbStatementGeneric;
|
||||
|
||||
/**
|
||||
* Generic statement.
|
||||
*/
|
||||
class JdbcStatementGeneric extends JdbcStatement<DbStatementGeneric, DbResult> implements DbStatementGeneric {
|
||||
|
||||
/** Local logger instance. */
|
||||
private static final Logger LOGGER = Logger.getLogger(JdbcStatementGeneric.class.getName());
|
||||
|
||||
private static final class GenericDbResult implements DbResult {
|
||||
|
||||
private final CompletableFuture<DbRows<DbRow>> queryResultFuture;
|
||||
private final CompletableFuture<Long> dmlResultFuture;
|
||||
private final CompletableFuture<Throwable> exceptionFuture;
|
||||
|
||||
GenericDbResult(
|
||||
final CompletableFuture<DbRows<DbRow>> queryResultFuture,
|
||||
final CompletableFuture<Long> dmlResultFuture,
|
||||
final CompletableFuture<Throwable> exceptionFuture
|
||||
) {
|
||||
this.queryResultFuture = queryResultFuture;
|
||||
this.dmlResultFuture = dmlResultFuture;
|
||||
this.exceptionFuture = exceptionFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbResult whenDml(Consumer<Long> consumer) {
|
||||
dmlResultFuture.thenAccept(consumer);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbResult whenRs(Consumer<DbRows<DbRow>> consumer) {
|
||||
queryResultFuture.thenAccept(consumer);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbResult exceptionally(Consumer<Throwable> exceptionHandler) {
|
||||
exceptionFuture.thenAccept(exceptionHandler);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Long> dmlFuture() {
|
||||
return dmlResultFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<DbRows<DbRow>> rsFuture() {
|
||||
return queryResultFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Throwable> exceptionFuture() {
|
||||
return exceptionFuture;
|
||||
}
|
||||
}
|
||||
|
||||
JdbcStatementGeneric(JdbcExecuteContext executeContext,
|
||||
JdbcStatementContext statementContext) {
|
||||
super(executeContext, statementContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CompletionStage<DbResult> doExecute(CompletionStage<DbInterceptorContext> dbContextFuture,
|
||||
CompletableFuture<Void> interceptorStatementFuture,
|
||||
CompletableFuture<Long> interceptorQueryFuture) {
|
||||
|
||||
executeContext().addFuture(interceptorQueryFuture);
|
||||
|
||||
CompletableFuture<DbRows<DbRow>> queryResultFuture = new CompletableFuture<>();
|
||||
CompletableFuture<Long> dmlResultFuture = new CompletableFuture<>();
|
||||
CompletableFuture<Throwable> exceptionFuture = new CompletableFuture<>();
|
||||
CompletableFuture<JdbcStatementQuery.ResultWithConn> resultSetFuture = new CompletableFuture<>();
|
||||
|
||||
dbContextFuture.exceptionally(throwable -> {
|
||||
resultSetFuture.completeExceptionally(throwable);
|
||||
|
||||
return null;
|
||||
});
|
||||
// this is completed on execution of statement
|
||||
resultSetFuture.exceptionally(throwable -> {
|
||||
interceptorStatementFuture.completeExceptionally(throwable);
|
||||
queryResultFuture.completeExceptionally(throwable);
|
||||
dmlResultFuture.completeExceptionally(throwable);
|
||||
exceptionFuture.completeExceptionally(throwable);
|
||||
return null;
|
||||
});
|
||||
|
||||
dbContextFuture.thenAccept(dbContext -> {
|
||||
// now let's execute the statement
|
||||
connection().thenAccept(conn -> {
|
||||
executorService().submit(() -> {
|
||||
try {
|
||||
PreparedStatement statement = super.build(conn, dbContext);
|
||||
|
||||
boolean isQuery = statement.execute();
|
||||
// statement is executed, we can finish the statement future
|
||||
interceptorStatementFuture.complete(null);
|
||||
|
||||
if (isQuery) {
|
||||
ResultSet resultSet = statement.getResultSet();
|
||||
// at this moment we have a DbRowResult
|
||||
resultSetFuture.complete(new JdbcStatementQuery.ResultWithConn(resultSet, conn));
|
||||
} else {
|
||||
try {
|
||||
long update = statement.getLargeUpdateCount();
|
||||
interceptorQueryFuture.complete(update);
|
||||
dmlResultFuture.complete(update);
|
||||
statement.close();
|
||||
} finally {
|
||||
conn.close();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (null != conn) {
|
||||
try {
|
||||
// we would not close the connection in the resultSetFuture, so we have to close it here
|
||||
conn.close();
|
||||
} catch (SQLException ex) {
|
||||
LOGGER.log(Level.WARNING,
|
||||
String.format("Failed to close connection: %s", ex.getMessage()),
|
||||
ex);
|
||||
}
|
||||
}
|
||||
resultSetFuture.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
TODO Too many futures
|
||||
Futures:
|
||||
interceptorStatementFuture - completed once we call the statement
|
||||
interceptorQueryFuture - completed once we read all records from a query (or finish a DML) - requires count
|
||||
queryResultFuture - completed once we know this is a result set and we have prepared the DbRowResult
|
||||
dmlResultFuture - completed once we know this is a DML statement
|
||||
exceptionFuture - completes in case of any exception
|
||||
resultSetFuture - completes if this is a result set and has the connection & result set objects
|
||||
*/
|
||||
// for DML - everything is finished and done
|
||||
/*
|
||||
For Query
|
||||
Ignored:
|
||||
dmlResultFuture
|
||||
Completed:
|
||||
interceptorStatementFuture
|
||||
resultSetFuture
|
||||
Open:
|
||||
interceptorQueryFuture
|
||||
queryResultFuture
|
||||
exceptionFuture
|
||||
*/
|
||||
|
||||
// and now, let's construct the DbRowResult
|
||||
resultSetFuture.thenAccept(rsAndConn -> {
|
||||
DbRows<DbRow> dbRows = JdbcStatementQuery.processResultSet(
|
||||
executorService(),
|
||||
dbMapperManager(),
|
||||
mapperManager(),
|
||||
interceptorQueryFuture,
|
||||
rsAndConn.resultSet());
|
||||
|
||||
interceptorQueryFuture.exceptionally(throwable -> {
|
||||
exceptionFuture.complete(throwable);
|
||||
return null;
|
||||
});
|
||||
queryResultFuture.complete(dbRows);
|
||||
}).exceptionally(throwable -> {
|
||||
interceptorQueryFuture.completeExceptionally(throwable);
|
||||
queryResultFuture.completeExceptionally(throwable);
|
||||
exceptionFuture.complete(throwable);
|
||||
return null;
|
||||
});
|
||||
|
||||
return interceptorStatementFuture.thenApply(nothing -> {
|
||||
return new GenericDbResult(queryResultFuture, dmlResultFuture, exceptionFuture);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.jdbc;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
import io.helidon.common.reactive.Single;
|
||||
import io.helidon.dbclient.DbRow;
|
||||
import io.helidon.dbclient.DbStatementGet;
|
||||
|
||||
/**
|
||||
* A JDBC get implementation.
|
||||
* Delegates to {@link io.helidon.dbclient.jdbc.JdbcStatementQuery} and processes the result using a subscriber
|
||||
* to read the first value.
|
||||
*/
|
||||
class JdbcStatementGet implements DbStatementGet {
|
||||
|
||||
private final JdbcStatementQuery query;
|
||||
|
||||
JdbcStatementGet(JdbcExecuteContext executeContext,
|
||||
JdbcStatementContext statementContext) {
|
||||
|
||||
this.query = new JdbcStatementQuery(executeContext,
|
||||
statementContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcStatementGet params(List<?> parameters) {
|
||||
query.params(parameters);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcStatementGet params(Map<String, ?> parameters) {
|
||||
query.params(parameters);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcStatementGet namedParam(Object parameters) {
|
||||
query.namedParam(parameters);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcStatementGet indexedParam(Object parameters) {
|
||||
query.indexedParam(parameters);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcStatementGet addParam(Object parameter) {
|
||||
query.addParam(parameter);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcStatementGet addParam(String name, Object parameter) {
|
||||
query.addParam(name, parameter);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<Optional<DbRow>> execute() {
|
||||
return query.execute()
|
||||
.thenApply(dbRows -> Single.from(dbRows.publisher()))
|
||||
.thenCompose(Single::toOptionalStage);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,573 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Flow;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import io.helidon.common.GenericType;
|
||||
import io.helidon.common.mapper.MapperException;
|
||||
import io.helidon.common.mapper.MapperManager;
|
||||
import io.helidon.common.reactive.Multi;
|
||||
import io.helidon.dbclient.DbClientException;
|
||||
import io.helidon.dbclient.DbColumn;
|
||||
import io.helidon.dbclient.DbInterceptorContext;
|
||||
import io.helidon.dbclient.DbMapperManager;
|
||||
import io.helidon.dbclient.DbRow;
|
||||
import io.helidon.dbclient.DbRows;
|
||||
import io.helidon.dbclient.DbStatementQuery;
|
||||
|
||||
/**
|
||||
* Implementation of query.
|
||||
*/
|
||||
class JdbcStatementQuery extends JdbcStatement<DbStatementQuery, DbRows<DbRow>> implements DbStatementQuery {
|
||||
|
||||
/** Local logger instance. */
|
||||
private static final Logger LOGGER = Logger.getLogger(JdbcStatementQuery.class.getName());
|
||||
|
||||
JdbcStatementQuery(JdbcExecuteContext executeContext,
|
||||
JdbcStatementContext statementContext) {
|
||||
super(executeContext, statementContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CompletionStage<DbRows<DbRow>> doExecute(CompletionStage<DbInterceptorContext> dbContextFuture,
|
||||
CompletableFuture<Void> statementFuture,
|
||||
CompletableFuture<Long> queryFuture) {
|
||||
|
||||
executeContext().addFuture(queryFuture);
|
||||
|
||||
CompletionStage<DbRows<DbRow>> result = dbContextFuture.thenCompose(interceptorContext -> {
|
||||
return connection().thenApply(conn -> {
|
||||
PreparedStatement statement = super.build(conn, interceptorContext);
|
||||
try {
|
||||
ResultSet rs = statement.executeQuery();
|
||||
// at this moment we have a DbRows
|
||||
statementFuture.complete(null);
|
||||
return processResultSet(executorService(),
|
||||
dbMapperManager(),
|
||||
mapperManager(),
|
||||
queryFuture,
|
||||
rs);
|
||||
} catch (SQLException e) {
|
||||
LOGGER.log(Level.FINEST,
|
||||
String.format("Failed to execute query %s: %s", statement.toString(), e.getMessage()),
|
||||
e);
|
||||
throw new DbClientException("Failed to execute query", e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
result.exceptionally(throwable -> {
|
||||
statementFuture.completeExceptionally(throwable);
|
||||
return null;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static DbRows<DbRow> processResultSet(
|
||||
ExecutorService executorService,
|
||||
DbMapperManager dbMapperManager,
|
||||
MapperManager mapperManager,
|
||||
CompletableFuture<Long> queryFuture,
|
||||
ResultSet resultSet) {
|
||||
|
||||
return new JdbcDbRows<>(
|
||||
resultSet,
|
||||
executorService,
|
||||
dbMapperManager,
|
||||
mapperManager,
|
||||
queryFuture,
|
||||
DbRow.class);
|
||||
}
|
||||
|
||||
static Map<Long, DbColumn> createMetadata(ResultSet rs) throws SQLException {
|
||||
ResultSetMetaData metaData = rs.getMetaData();
|
||||
int columnCount = metaData.getColumnCount();
|
||||
|
||||
Map<Long, DbColumn> byNumbers = new HashMap<>();
|
||||
|
||||
for (int i = 1; i <= columnCount; i++) {
|
||||
String name = metaData.getColumnName(i);
|
||||
String sqlType = metaData.getColumnTypeName(i);
|
||||
Class<?> javaClass = classByName(metaData.getColumnClassName(i));
|
||||
DbColumn column = new DbColumn() {
|
||||
@Override
|
||||
public <T> T as(Class<T> type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T as(GenericType<T> type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> javaType() {
|
||||
return javaClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String dbType() {
|
||||
return sqlType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
};
|
||||
byNumbers.put((long) i, column);
|
||||
}
|
||||
return byNumbers;
|
||||
}
|
||||
|
||||
private static Class<?> classByName(String columnClassName) {
|
||||
if (columnClassName == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Class.forName(columnClassName);
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String name() {
|
||||
return statementName();
|
||||
}
|
||||
|
||||
private static final class JdbcDbRows<T> implements DbRows<T> {
|
||||
private final AtomicBoolean resultRequested = new AtomicBoolean();
|
||||
private final ExecutorService executorService;
|
||||
private final DbMapperManager dbMapperManager;
|
||||
private final MapperManager mapperManager;
|
||||
private final CompletableFuture<Long> queryFuture;
|
||||
private final ResultSet resultSet;
|
||||
private final JdbcDbRows<?> parent;
|
||||
private final GenericType<T> currentType;
|
||||
private final Function<?, T> resultMapper;
|
||||
|
||||
private JdbcDbRows(ResultSet resultSet,
|
||||
ExecutorService executorService,
|
||||
DbMapperManager dbMapperManager,
|
||||
MapperManager mapperManager,
|
||||
CompletableFuture<Long> queryFuture,
|
||||
Class<T> initialType) {
|
||||
|
||||
this(resultSet,
|
||||
executorService,
|
||||
dbMapperManager,
|
||||
mapperManager,
|
||||
queryFuture,
|
||||
GenericType.create(initialType),
|
||||
Function.identity(),
|
||||
null);
|
||||
}
|
||||
|
||||
private JdbcDbRows(ResultSet resultSet,
|
||||
ExecutorService executorService,
|
||||
DbMapperManager dbMapperManager,
|
||||
MapperManager mapperManager,
|
||||
CompletableFuture<Long> queryFuture,
|
||||
GenericType<T> nextType,
|
||||
Function<?, T> resultMapper,
|
||||
JdbcDbRows<?> parent) {
|
||||
|
||||
this.executorService = executorService;
|
||||
this.dbMapperManager = dbMapperManager;
|
||||
this.mapperManager = mapperManager;
|
||||
this.queryFuture = queryFuture;
|
||||
this.resultSet = resultSet;
|
||||
this.currentType = nextType;
|
||||
this.resultMapper = resultMapper;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> DbRows<U> map(Function<T, U> mapper) {
|
||||
return new JdbcDbRows<>(resultSet,
|
||||
executorService,
|
||||
dbMapperManager,
|
||||
mapperManager,
|
||||
queryFuture,
|
||||
null,
|
||||
mapper,
|
||||
this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> DbRows<U> map(Class<U> type) {
|
||||
return map(GenericType.create(type));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> DbRows<U> map(GenericType<U> type) {
|
||||
GenericType<T> currentType = this.currentType;
|
||||
|
||||
Function<T, U> theMapper;
|
||||
|
||||
if (null == currentType) {
|
||||
theMapper = value -> mapperManager.map(value,
|
||||
GenericType.create(value.getClass()),
|
||||
type);
|
||||
} else if (currentType.equals(DbMapperManager.TYPE_DB_ROW)) {
|
||||
// maybe we want the same type
|
||||
if (type.equals(DbMapperManager.TYPE_DB_ROW)) {
|
||||
return (DbRows<U>) this;
|
||||
}
|
||||
// try to find mapper in db mapper manager
|
||||
theMapper = value -> {
|
||||
//first try db mapper
|
||||
try {
|
||||
return dbMapperManager.read((DbRow) value, type);
|
||||
} catch (MapperException originalException) {
|
||||
// not found in db mappers, use generic mappers
|
||||
try {
|
||||
return mapperManager.map(value,
|
||||
DbMapperManager.TYPE_DB_ROW,
|
||||
type);
|
||||
} catch (MapperException ignored) {
|
||||
throw originalException;
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// one type to another
|
||||
theMapper = value -> mapperManager.map(value,
|
||||
currentType,
|
||||
type);
|
||||
}
|
||||
return new JdbcDbRows<>(resultSet,
|
||||
executorService,
|
||||
dbMapperManager,
|
||||
mapperManager,
|
||||
queryFuture,
|
||||
type,
|
||||
theMapper,
|
||||
this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flow.Publisher<T> publisher() {
|
||||
checkResult();
|
||||
return toPublisher();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionStage<List<T>> collect() {
|
||||
checkResult();
|
||||
return toFuture();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Flow.Publisher<T> toPublisher() {
|
||||
if (null == parent) {
|
||||
// this is DbRow type
|
||||
return (Flow.Publisher<T>) new RowPublisher(executorService,
|
||||
resultSet,
|
||||
queryFuture,
|
||||
dbMapperManager,
|
||||
mapperManager);
|
||||
}
|
||||
|
||||
Function<Object, T> mappingFunction = (Function<Object, T>) resultMapper;
|
||||
Multi<Object> parentMulti = (Multi<Object>) parent.multi();
|
||||
|
||||
return parentMulti.map(mappingFunction::apply);
|
||||
}
|
||||
|
||||
private Multi<T> multi() {
|
||||
return Multi.from(publisher());
|
||||
}
|
||||
|
||||
private CompletionStage<List<T>> toFuture() {
|
||||
|
||||
return Multi.from(toPublisher())
|
||||
.collectList()
|
||||
.toStage();
|
||||
}
|
||||
|
||||
private void checkResult() {
|
||||
if (resultRequested.get()) {
|
||||
throw new IllegalStateException("Result has already been requested");
|
||||
}
|
||||
resultRequested.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class RowPublisher implements Flow.Publisher<DbRow> {
|
||||
private final ExecutorService executorService;
|
||||
private final ResultSet rs;
|
||||
private final CompletableFuture<Long> queryFuture;
|
||||
private final DbMapperManager dbMapperManager;
|
||||
private final MapperManager mapperManager;
|
||||
|
||||
private RowPublisher(ExecutorService executorService,
|
||||
ResultSet rs,
|
||||
CompletableFuture<Long> queryFuture,
|
||||
DbMapperManager dbMapperManager,
|
||||
MapperManager mapperManager) {
|
||||
|
||||
this.executorService = executorService;
|
||||
this.rs = rs;
|
||||
this.queryFuture = queryFuture;
|
||||
this.dbMapperManager = dbMapperManager;
|
||||
this.mapperManager = mapperManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribe(Flow.Subscriber<? super DbRow> subscriber) {
|
||||
LinkedBlockingQueue<Long> requestQueue = new LinkedBlockingQueue<>();
|
||||
AtomicBoolean cancelled = new AtomicBoolean();
|
||||
|
||||
// we have executed the statement, we can correctly subscribe
|
||||
subscriber.onSubscribe(new Flow.Subscription() {
|
||||
@Override
|
||||
public void request(long n) {
|
||||
// add the requested number to the queue
|
||||
requestQueue.add(n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
cancelled.set(true);
|
||||
requestQueue.clear();
|
||||
}
|
||||
});
|
||||
|
||||
// TODO
|
||||
// we should only use a thread to read data that was actually requested
|
||||
// I would prefer to use the same thread to process a single query (to make sure we honor thread locals
|
||||
// that may be used by the database)
|
||||
|
||||
// and now we can process the data from the database
|
||||
executorService.submit(() -> {
|
||||
//now we have a subscriber, we can handle the processing of result set
|
||||
try (ResultSet rs = this.rs) {
|
||||
Map<Long, DbColumn> metadata = createMetadata(rs);
|
||||
long count = 0;
|
||||
|
||||
// now we only want to process next record if it was requested
|
||||
while (!cancelled.get()) {
|
||||
Long nextElement;
|
||||
try {
|
||||
nextElement = requestQueue.poll(10, TimeUnit.MINUTES);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.finest("Interrupted while polling for requests, terminating DB read");
|
||||
subscriber.onError(e);
|
||||
break;
|
||||
}
|
||||
if (nextElement == null) {
|
||||
LOGGER.finest("No data requested for 10 minutes, terminating DB read");
|
||||
subscriber.onError(new TimeoutException("No data requested in 10 minutes"));
|
||||
break;
|
||||
}
|
||||
for (long i = 0; i < nextElement; i++) {
|
||||
if (rs.next()) {
|
||||
DbRow dbRow = createDbRow(rs, metadata, dbMapperManager, mapperManager);
|
||||
subscriber.onNext(dbRow);
|
||||
count++;
|
||||
} else {
|
||||
queryFuture.complete(count);
|
||||
subscriber.onComplete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cancelled.get()) {
|
||||
queryFuture
|
||||
.completeExceptionally(new CancellationException("Processing cancelled by subscriber"));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
queryFuture.completeExceptionally(e);
|
||||
subscriber.onError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private DbRow createDbRow(ResultSet rs,
|
||||
Map<Long, DbColumn> metadata,
|
||||
DbMapperManager dbMapperManager,
|
||||
MapperManager mapperManager) throws SQLException {
|
||||
// read whole row
|
||||
// for each column
|
||||
Map<String, DbColumn> byStringsWithValues = new HashMap<>();
|
||||
Map<Integer, DbColumn> byNumbersWithValues = new HashMap<>();
|
||||
|
||||
for (int i = 1; i <= metadata.size(); i++) {
|
||||
DbColumn meta = metadata.get((long) i);
|
||||
Object value = rs.getObject(i);
|
||||
DbColumn withValue = new DbColumn() {
|
||||
@Override
|
||||
public <T> T as(Class<T> type) {
|
||||
if (null == value) {
|
||||
return null;
|
||||
}
|
||||
if (type.isAssignableFrom(value.getClass())) {
|
||||
return type.cast(value);
|
||||
}
|
||||
return map(value, type);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
<SRC, T> T map(SRC value, Class<T> type) {
|
||||
Class<SRC> theClass = (Class<SRC>) value.getClass();
|
||||
return mapperManager.map(value, theClass, type);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
<SRC, T> T map(SRC value, GenericType<T> type) {
|
||||
Class<SRC> theClass = (Class<SRC>) value.getClass();
|
||||
return mapperManager.map(value, GenericType.create(theClass), type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T as(GenericType<T> type) {
|
||||
if (null == value) {
|
||||
return null;
|
||||
}
|
||||
if (type.isClass()) {
|
||||
Class<?> theClass = type.rawType();
|
||||
if (theClass.isAssignableFrom(value.getClass())) {
|
||||
return type.cast(value);
|
||||
}
|
||||
}
|
||||
return map(value, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> javaType() {
|
||||
if (null == meta.javaType()) {
|
||||
if (null == value) {
|
||||
return null;
|
||||
}
|
||||
return value.getClass();
|
||||
} else {
|
||||
return meta.javaType();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String dbType() {
|
||||
return meta.dbType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return meta.name();
|
||||
}
|
||||
};
|
||||
byStringsWithValues.put(meta.name(), withValue);
|
||||
byNumbersWithValues.put(i, withValue);
|
||||
}
|
||||
|
||||
return new DbRow() {
|
||||
@Override
|
||||
public DbColumn column(String name) {
|
||||
return byStringsWithValues.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbColumn column(int index) {
|
||||
return byNumbersWithValues.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<? super DbColumn> columnAction) {
|
||||
byStringsWithValues.values()
|
||||
.forEach(columnAction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T as(Class<T> type) {
|
||||
return dbMapperManager.read(this, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T as(GenericType<T> type) {
|
||||
return dbMapperManager.read(this, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T as(Function<DbRow, T> mapper) {
|
||||
return mapper.apply(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean first = true;
|
||||
sb.append('{');
|
||||
for (DbColumn col : byStringsWithValues.values()) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append(col.name());
|
||||
sb.append(':');
|
||||
sb.append(col.value().toString());
|
||||
}
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static final class ResultWithConn {
|
||||
private final ResultSet resultSet;
|
||||
private final Connection connection;
|
||||
|
||||
ResultWithConn(ResultSet resultSet, Connection connection) {
|
||||
this.resultSet = resultSet;
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
public ResultSet resultSet() {
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
public Connection connection() {
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
/**
|
||||
* Helidon DB implementation for JDBC.
|
||||
*/
|
||||
package io.helidon.dbclient.jdbc;
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.jdbc.spi;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.dbclient.jdbc.HikariCpExtension;
|
||||
|
||||
/**
|
||||
* Java Service loader interface that provides JDBC DB Client configuration extension.
|
||||
*/
|
||||
public interface HikariCpExtensionProvider {
|
||||
/**
|
||||
* Configuration key of the extension provider.
|
||||
* @return configuration key expected under {@code connection.helidon}
|
||||
*/
|
||||
String configKey();
|
||||
|
||||
/**
|
||||
* Get instance of JDBC DB Client configuration extension.
|
||||
*
|
||||
* @param config configuration of this provider to obtain an extension instance
|
||||
* @return JDBC DB Client configuration extension
|
||||
*/
|
||||
HikariCpExtension extension(Config config);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
/**
|
||||
* Service provider interface for Helidon DB implementation for JDBC.
|
||||
*
|
||||
* The main entry point for JDBC DB Client configuration interceptors implementation
|
||||
* is {@link io.helidon.dbclient.jdbc.spi.HikariCpExtensionProvider}.
|
||||
*/
|
||||
package io.helidon.dbclient.jdbc.spi;
|
||||
37
dbclient/jdbc/src/main/java/module-info.java
Normal file
37
dbclient/jdbc/src/main/java/module-info.java
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
import io.helidon.dbclient.jdbc.spi.HikariCpExtensionProvider;
|
||||
|
||||
/**
|
||||
* Helidon Common Mapper.
|
||||
*/
|
||||
module io.helidon.dbclient.jdbc {
|
||||
uses HikariCpExtensionProvider;
|
||||
requires java.logging;
|
||||
requires java.sql;
|
||||
requires com.zaxxer.hikari;
|
||||
|
||||
requires transitive io.helidon.common;
|
||||
requires transitive io.helidon.common.configurable;
|
||||
requires transitive io.helidon.dbclient;
|
||||
requires transitive io.helidon.dbclient.common;
|
||||
|
||||
exports io.helidon.dbclient.jdbc;
|
||||
exports io.helidon.dbclient.jdbc.spi;
|
||||
|
||||
provides io.helidon.dbclient.spi.DbClientProvider with io.helidon.dbclient.jdbc.JdbcDbClientProvider;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#
|
||||
# Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
#
|
||||
# Licensed 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.
|
||||
#
|
||||
|
||||
io.helidon.dbclient.jdbc.JdbcDbClientProvider
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.jdbc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.helidon.dbclient.jdbc.JdbcStatement.Parser;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Unit test for {@link JdbcStatement.Parser}.
|
||||
* Tests must cover as large automaton states space as possible.
|
||||
*/
|
||||
public class JdbcStatementParserTest {
|
||||
|
||||
/**
|
||||
* Test simple SQL statement without parameters.
|
||||
* String parsing shall go trough all local transitions without leaving STMT and STR states.
|
||||
*/
|
||||
@Test
|
||||
void testStatementWithNoParameter() {
|
||||
String stmtIn =
|
||||
"SELECT *, 2 FROM table\r\n" +
|
||||
" WHERE name LIKE 'a?e%'\n";
|
||||
Parser parser = new Parser(stmtIn);
|
||||
String stmtOut = parser.convert();
|
||||
List<String> names= parser.namesOrder();
|
||||
assertEquals(stmtIn, stmtOut);
|
||||
assertTrue(names.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test simple SQL statement with parameters.
|
||||
* Parameters contain both letters and numbers in proper order.
|
||||
*/
|
||||
@Test
|
||||
void testStatementWithParameters() {
|
||||
String stmtIn =
|
||||
"SELECT t.*, 'first' FROM table t\r\n" +
|
||||
" WHERE name = :n4m3\n" +
|
||||
" AND age > :ag3";
|
||||
String stmtExp =
|
||||
"SELECT t.*, 'first' FROM table t\r\n" +
|
||||
" WHERE name = ?\n" +
|
||||
" AND age > ?";
|
||||
List<String> namesExp = new ArrayList<>(2);
|
||||
namesExp.add("n4m3");
|
||||
namesExp.add("ag3");
|
||||
Parser parser = new Parser(stmtIn);
|
||||
String stmtOut = parser.convert();
|
||||
List<String> names= parser.namesOrder();
|
||||
assertEquals(stmtExp, stmtOut);
|
||||
assertEquals(namesExp, names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test simple SQL statement with parameters inside multi-line comment.
|
||||
* Only parameters outside comments shall be returned.
|
||||
*/
|
||||
@Test
|
||||
void testStatementWithParametersInMultiLineCommnet() {
|
||||
String stmtIn =
|
||||
"SELECT t.*, 'first' FROM table t /* Parameter for name is :n4me\r\n" +
|
||||
" and for age is :ag3 */\n" +
|
||||
" WHERE address IS NULL\r\n" +
|
||||
" AND name = :n4m3\n" +
|
||||
" AND age > :ag3";
|
||||
String stmtExp =
|
||||
"SELECT t.*, 'first' FROM table t /* Parameter for name is :n4me\r\n" +
|
||||
" and for age is :ag3 */\n" +
|
||||
" WHERE address IS NULL\r\n" +
|
||||
" AND name = ?\n" +
|
||||
" AND age > ?";
|
||||
List<String> namesExp = new ArrayList<>(2);
|
||||
namesExp.add("n4m3");
|
||||
namesExp.add("ag3");
|
||||
Parser parser = new Parser(stmtIn);
|
||||
String stmtOut = parser.convert();
|
||||
List<String> names= parser.namesOrder();
|
||||
assertEquals(stmtExp, stmtOut);
|
||||
assertEquals(namesExp, names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test simple SQL statement with parameters inside multi-line comment.
|
||||
* Only parameters outside comments shall be returned.
|
||||
*/
|
||||
@Test
|
||||
void testStatementWithParametersInSingleLineCommnet() {
|
||||
String stmtIn =
|
||||
"SELECT t.*, 'first' FROM table t -- Parameter for name is :n4me\r\r\n" +
|
||||
" WHERE address IS NULL\r\n" +
|
||||
" AND name = :myN4m3\n" +
|
||||
" AND age > :ag3";
|
||||
String stmtExp =
|
||||
"SELECT t.*, 'first' FROM table t -- Parameter for name is :n4me\r\r\n" +
|
||||
" WHERE address IS NULL\r\n" +
|
||||
" AND name = ?\n" +
|
||||
" AND age > ?";
|
||||
List<String> namesExp = new ArrayList<>(2);
|
||||
namesExp.add("myN4m3");
|
||||
namesExp.add("ag3");
|
||||
Parser parser = new Parser(stmtIn);
|
||||
String stmtOut = parser.convert();
|
||||
List<String> names= parser.namesOrder();
|
||||
assertEquals(stmtExp, stmtOut);
|
||||
assertEquals(namesExp, names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test simple SQL statement with valid and invalid name parameters.
|
||||
* Only parameters outside comments shall be returned.
|
||||
*/
|
||||
@Test
|
||||
void testStatementWithValidandinvalidParameters() {
|
||||
String stmtIn =
|
||||
"SELECT p.firstName, p.secondName, a.street, a.towm" +
|
||||
" FROM person p" +
|
||||
" INNER JOIN address a ON a.id = p.aid" +
|
||||
" WHERE p.age > :12age" +
|
||||
" AND a.zip = :zip";
|
||||
String stmtExp =
|
||||
"SELECT p.firstName, p.secondName, a.street, a.towm" +
|
||||
" FROM person p" +
|
||||
" INNER JOIN address a ON a.id = p.aid" +
|
||||
" WHERE p.age > :12age" +
|
||||
" AND a.zip = ?";
|
||||
List<String> namesExp = new ArrayList<>(2);
|
||||
namesExp.add("zip");
|
||||
Parser parser = new Parser(stmtIn);
|
||||
String stmtOut = parser.convert();
|
||||
List<String> names= parser.namesOrder();
|
||||
assertEquals(stmtExp, stmtOut);
|
||||
assertEquals(namesExp, names);
|
||||
}
|
||||
|
||||
}
|
||||
45
dbclient/jsonp/pom.xml
Normal file
45
dbclient/jsonp/pom.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
Licensed 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>helidon-dbclient-project</artifactId>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>helidon-dbclient-jsonp</artifactId>
|
||||
<name>Helidon DB Client JSON-Processing</name>
|
||||
|
||||
<description>
|
||||
Support for JSON-Processing as parameters and responses in Helidon DB
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish</groupId>
|
||||
<artifactId>javax.json</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.jsonp;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.json.Json;
|
||||
import javax.json.JsonBuilderFactory;
|
||||
import javax.json.JsonObject;
|
||||
import javax.json.JsonObjectBuilder;
|
||||
import javax.json.JsonValue;
|
||||
|
||||
import io.helidon.dbclient.DbMapper;
|
||||
import io.helidon.dbclient.DbRow;
|
||||
|
||||
/**
|
||||
* Json processing mapper.
|
||||
*/
|
||||
public final class JsonProcessingMapper implements DbMapper<JsonObject> {
|
||||
private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
|
||||
private static final Map<Class<?>, DbJsonWriter> JSON_WRITERS = new IdentityHashMap<>();
|
||||
private static final DbJsonWriter NUMBER_WRITER = (builder, name, value) -> builder.add(name, ((Number) value).longValue());
|
||||
private static final DbJsonWriter OBJECT_WRITER = (builder, name, value) -> builder.add(name, String.valueOf(value));
|
||||
|
||||
static {
|
||||
JSON_WRITERS.put(Integer.class, (builder, name, value) -> builder.add(name, (Integer) value));
|
||||
JSON_WRITERS.put(Short.class, (builder, name, value) -> builder.add(name, (Short) value));
|
||||
JSON_WRITERS.put(Byte.class, (builder, name, value) -> builder.add(name, (Byte) value));
|
||||
JSON_WRITERS.put(AtomicInteger.class, (builder, name, value) -> builder.add(name, ((AtomicInteger) value).get()));
|
||||
JSON_WRITERS.put(Float.class, (builder, name, value) -> builder.add(name, (Float) value));
|
||||
JSON_WRITERS.put(Double.class, (builder, name, value) -> builder.add(name, (Double) value));
|
||||
JSON_WRITERS.put(BigInteger.class, (builder, name, value) -> builder.add(name, (BigInteger) value));
|
||||
JSON_WRITERS.put(BigDecimal.class, (builder, name, value) -> builder.add(name, (BigDecimal) value));
|
||||
JSON_WRITERS.put(Long.class, (builder, name, value) -> builder.add(name, (Long) value));
|
||||
JSON_WRITERS.put(String.class, (builder, name, value) -> builder.add(name, (String) value));
|
||||
JSON_WRITERS.put(Boolean.class, (builder, name, value) -> builder.add(name, (Boolean) value));
|
||||
// primitives
|
||||
JSON_WRITERS.put(int.class, JSON_WRITERS.get(Integer.class));
|
||||
JSON_WRITERS.put(short.class, JSON_WRITERS.get(Short.class));
|
||||
JSON_WRITERS.put(byte.class, JSON_WRITERS.get(Byte.class));
|
||||
JSON_WRITERS.put(float.class, JSON_WRITERS.get(Float.class));
|
||||
JSON_WRITERS.put(double.class, JSON_WRITERS.get(Double.class));
|
||||
JSON_WRITERS.put(long.class, JSON_WRITERS.get(Long.class));
|
||||
JSON_WRITERS.put(boolean.class, JSON_WRITERS.get(Boolean.class));
|
||||
}
|
||||
|
||||
private JsonProcessingMapper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new mapper that can map {@link javax.json.JsonObject} to DB parameters and {@link io.helidon.dbclient.DbRow}
|
||||
* to a {@link javax.json.JsonObject}.
|
||||
*
|
||||
* @return a new mapper
|
||||
*/
|
||||
public static JsonProcessingMapper create() {
|
||||
return new JsonProcessingMapper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JSON-P representation of this row.
|
||||
*
|
||||
* @return json object containing column name to column value.
|
||||
*/
|
||||
@Override
|
||||
public JsonObject read(DbRow row) {
|
||||
JsonObjectBuilder objectBuilder = JSON.createObjectBuilder();
|
||||
row.forEach(dbCol -> toJson(objectBuilder, dbCol.name(), dbCol.javaType(), dbCol.value()));
|
||||
return objectBuilder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> toNamedParameters(JsonObject value) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
value.forEach((name, json) -> result.put(name, toObject(name, json, value)));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> toIndexedParameters(JsonObject value) {
|
||||
// in case the underlying map is linked, we can do this
|
||||
// obviously the number of parameters must match the number in statement, so most likely this is
|
||||
// going to fail
|
||||
List<Object> result = new LinkedList<>();
|
||||
value.forEach((name, json) -> result.add(toObject(name, json, value)));
|
||||
return result;
|
||||
}
|
||||
|
||||
private void toJson(JsonObjectBuilder objectBuilder, String name, Class<?> valueClass, Object value) {
|
||||
if (value == null) {
|
||||
objectBuilder.addNull(name);
|
||||
}
|
||||
getJsonWriter(valueClass).write(objectBuilder, name, value);
|
||||
}
|
||||
|
||||
private DbJsonWriter getJsonWriter(Class<?> valueClass) {
|
||||
DbJsonWriter writer = JSON_WRITERS.get(valueClass);
|
||||
if (null != writer) {
|
||||
return writer;
|
||||
}
|
||||
if (Number.class.isAssignableFrom(valueClass)) {
|
||||
return NUMBER_WRITER;
|
||||
}
|
||||
return OBJECT_WRITER;
|
||||
}
|
||||
|
||||
private Object toObject(String name, JsonValue json, JsonObject jsonObject) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
switch (json.getValueType()) {
|
||||
case STRING:
|
||||
return jsonObject.getString(name);
|
||||
case NUMBER:
|
||||
return jsonObject.getJsonNumber(name).numberValue();
|
||||
case TRUE:
|
||||
return Boolean.TRUE;
|
||||
case FALSE:
|
||||
return Boolean.FALSE;
|
||||
case NULL:
|
||||
return null;
|
||||
case OBJECT:
|
||||
return jsonObject.getJsonObject(name);
|
||||
case ARRAY:
|
||||
return jsonObject.getJsonArray(name);
|
||||
default:
|
||||
throw new IllegalStateException(String.format("Unknown JSON value type: %s", json.getValueType()));
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface DbJsonWriter {
|
||||
void write(JsonObjectBuilder objectBuilder, String name, Object value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.jsonp;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.annotation.Priority;
|
||||
import javax.json.JsonObject;
|
||||
|
||||
import io.helidon.common.Prioritized;
|
||||
import io.helidon.dbclient.DbMapper;
|
||||
import io.helidon.dbclient.spi.DbMapperProvider;
|
||||
|
||||
/**
|
||||
* JSON-P mapper provider.
|
||||
*/
|
||||
@Priority(Prioritized.DEFAULT_PRIORITY)
|
||||
public class JsonProcessingMapperProvider implements DbMapperProvider {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> Optional<DbMapper<T>> mapper(Class<T> type) {
|
||||
if (type.equals(JsonObject.class)) {
|
||||
return Optional.of((DbMapper<T>) JsonProcessingMapper.create());
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
/**
|
||||
* JSON Processing support for Helidon DB.
|
||||
*/
|
||||
package io.helidon.dbclient.jsonp;
|
||||
29
dbclient/jsonp/src/main/java/module-info.java
Normal file
29
dbclient/jsonp/src/main/java/module-info.java
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helidon DB JSON-P Mapper.
|
||||
*/
|
||||
module io.helidon.dbclient.jsonp {
|
||||
requires java.logging;
|
||||
requires io.helidon.dbclient;
|
||||
requires java.json;
|
||||
|
||||
exports io.helidon.dbclient.jsonp;
|
||||
|
||||
provides io.helidon.dbclient.spi.DbMapperProvider with io.helidon.dbclient.jsonp.JsonProcessingMapperProvider;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
#
|
||||
# Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
#
|
||||
# Licensed 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.
|
||||
#
|
||||
|
||||
io.helidon.dbclient.jsonp.JsonProcessingMapperProvider
|
||||
55
dbclient/metrics-jdbc/pom.xml
Normal file
55
dbclient/metrics-jdbc/pom.xml
Normal file
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
Licensed 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient-project</artifactId>
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>helidon-dbclient-metrics-jdbc</artifactId>
|
||||
<name>Helidon DB Client JDBC Metrics</name>
|
||||
|
||||
<description>Metrics support for Helidon DB implementation for JDBC</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient-metrics</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.metrics</groupId>
|
||||
<artifactId>helidon-metrics</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
<artifactId>metrics-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.metrics.jdbc;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.metrics.RegistryFactory;
|
||||
|
||||
import com.codahale.metrics.Counter;
|
||||
import com.codahale.metrics.Gauge;
|
||||
import com.codahale.metrics.Histogram;
|
||||
import com.codahale.metrics.Meter;
|
||||
import com.codahale.metrics.MetricRegistryListener;
|
||||
import com.codahale.metrics.Timer;
|
||||
import org.eclipse.microprofile.metrics.MetricRegistry;
|
||||
|
||||
/**
|
||||
* Hikari CP to Helidon metrics mapper.
|
||||
*
|
||||
* Listeners for events from the metrics registry and (un)registers metrics instances in Helidon.
|
||||
*/
|
||||
public class DropwizardMetricsListener implements MetricRegistryListener {
|
||||
|
||||
/** Local logger instance. */
|
||||
private static final Logger LOGGER = Logger.getLogger(DropwizardMetricsListener.class.getName());
|
||||
|
||||
private final String prefix;
|
||||
// Helidon metrics registry
|
||||
private final MetricRegistry registry;
|
||||
|
||||
private DropwizardMetricsListener(String prefix) {
|
||||
this.prefix = prefix;
|
||||
this.registry = RegistryFactory.getInstance().getRegistry(MetricRegistry.Type.VENDOR);
|
||||
}
|
||||
|
||||
static MetricRegistryListener create(Config config) {
|
||||
return new DropwizardMetricsListener(config.get("name-prefix").asString().orElse("db.pool."));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGaugeAdded(String name, Gauge<?> gauge) {
|
||||
LOGGER.finest(() -> String.format("Gauge added: %s", name));
|
||||
registry.register(prefix + name, new JdbcMetricsGauge<>(gauge));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGaugeRemoved(String name) {
|
||||
LOGGER.finest(() -> String.format("Gauge removed: %s", name));
|
||||
registry.remove(prefix + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCounterAdded(String name, Counter counter) {
|
||||
LOGGER.finest(() -> String.format("Counter added: %s", name));
|
||||
registry.register(prefix + name, new JdbcMetricsCounter(counter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCounterRemoved(String name) {
|
||||
LOGGER.finest(() -> String.format("Counter removed: %s", name));
|
||||
registry.remove(prefix + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHistogramAdded(String name, Histogram histogram) {
|
||||
LOGGER.finest(() -> String.format("Histogram added: %s", name));
|
||||
registry.register(prefix + name, new JdbcMetricsHistogram(histogram));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHistogramRemoved(String name) {
|
||||
LOGGER.finest(() -> String.format("Histogram removed: %s", name));
|
||||
registry.remove(prefix + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMeterAdded(String name, Meter meter) {
|
||||
LOGGER.finest(() -> String.format("Meter added: %s", name));
|
||||
registry.register(prefix + name, new JdbcMetricsMeter(meter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMeterRemoved(String name) {
|
||||
LOGGER.finest(() -> String.format("Meter removed: %s", name));
|
||||
registry.remove(prefix + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimerAdded(String name, Timer timer) {
|
||||
LOGGER.finest(() -> String.format("Timer added: %s", name));
|
||||
registry.register(prefix + name, new JdbcMetricsTimer(timer));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimerRemoved(String name) {
|
||||
LOGGER.finest(() -> String.format("Timer removed: %s", name));
|
||||
registry.remove(prefix + name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.metrics.jdbc;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.dbclient.jdbc.HikariCpExtension;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
|
||||
/**
|
||||
* JDBC Configuration Interceptor for Metrics.
|
||||
*
|
||||
* Registers JDBC connection pool metrics to {@code HikariConnectionPool}.
|
||||
*/
|
||||
final class HikariMetricsExtension implements HikariCpExtension {
|
||||
private final Config config;
|
||||
private final boolean enabled;
|
||||
|
||||
private HikariMetricsExtension(Config config, boolean enabled) {
|
||||
this.config = config;
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
static HikariMetricsExtension create(Config config) {
|
||||
return new HikariMetricsExtension(config, config.get("enabled").asBoolean().orElse(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register {@code MetricRegistry} instance with listener into Hikari CP configuration.
|
||||
*
|
||||
* @param poolConfig Hikari CP configuration
|
||||
*/
|
||||
@Override
|
||||
public void configure(HikariConfig poolConfig) {
|
||||
if (enabled) {
|
||||
final MetricRegistry metricRegistry = new MetricRegistry();
|
||||
metricRegistry.addListener(DropwizardMetricsListener.create(config));
|
||||
poolConfig.setMetricRegistry(metricRegistry);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.metrics.jdbc;
|
||||
|
||||
import org.eclipse.microprofile.metrics.Counter;
|
||||
|
||||
/**
|
||||
* {@link Counter} metric wrapper for Hikari CP metric.
|
||||
*/
|
||||
public class JdbcMetricsCounter implements Counter {
|
||||
|
||||
private final com.codahale.metrics.Counter counter;
|
||||
|
||||
JdbcMetricsCounter(final com.codahale.metrics.Counter counter) {
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inc() {
|
||||
counter.inc();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inc(long n) {
|
||||
counter.inc(n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCount() {
|
||||
return counter.getCount();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.metrics.jdbc;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.dbclient.jdbc.HikariCpExtension;
|
||||
import io.helidon.dbclient.jdbc.spi.HikariCpExtensionProvider;
|
||||
|
||||
/**
|
||||
* JDBC Configuration Interceptor Provider for Metrics.
|
||||
*
|
||||
* Returns JDBC Configuration Interceptor instance on request.
|
||||
*/
|
||||
public class JdbcMetricsExtensionProvider implements HikariCpExtensionProvider {
|
||||
@Override
|
||||
public String configKey() {
|
||||
return "pool-metrics";
|
||||
}
|
||||
|
||||
@Override
|
||||
public HikariCpExtension extension(Config config) {
|
||||
return HikariMetricsExtension.create(config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.metrics.jdbc;
|
||||
|
||||
import org.eclipse.microprofile.metrics.Gauge;
|
||||
|
||||
/**
|
||||
* {@link Gauge} metric wrapper for Hikari CP metric.
|
||||
*
|
||||
* @param <T> the type of the metric's value
|
||||
*/
|
||||
public class JdbcMetricsGauge<T> implements Gauge<T> {
|
||||
|
||||
private final com.codahale.metrics.Gauge<T> gauge;
|
||||
|
||||
JdbcMetricsGauge(final com.codahale.metrics.Gauge<T> counter) {
|
||||
this.gauge = counter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getValue() {
|
||||
return gauge.getValue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.metrics.jdbc;
|
||||
|
||||
import org.eclipse.microprofile.metrics.Histogram;
|
||||
import org.eclipse.microprofile.metrics.Snapshot;
|
||||
|
||||
/**
|
||||
* {@link Histogram} metric wrapper for Hikari CP metric.
|
||||
*/
|
||||
public class JdbcMetricsHistogram implements Histogram {
|
||||
|
||||
private final com.codahale.metrics.Histogram histogram;
|
||||
|
||||
JdbcMetricsHistogram(final com.codahale.metrics.Histogram histogram) {
|
||||
this.histogram = histogram;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(int value) {
|
||||
histogram.update(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(long value) {
|
||||
histogram.update(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCount() {
|
||||
return histogram.getCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Snapshot getSnapshot() {
|
||||
return new JdbcMetricsSnapshot(histogram.getSnapshot());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.metrics.jdbc;
|
||||
|
||||
import org.eclipse.microprofile.metrics.Meter;
|
||||
|
||||
/**
|
||||
* {@link Meter} metric wrapper for Hikari CP metric.
|
||||
*/
|
||||
public class JdbcMetricsMeter implements Meter {
|
||||
|
||||
private final com.codahale.metrics.Meter meter;
|
||||
|
||||
JdbcMetricsMeter(final com.codahale.metrics.Meter meter) {
|
||||
this.meter = meter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mark() {
|
||||
meter.mark();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mark(long n) {
|
||||
meter.mark(n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCount() {
|
||||
return meter.getCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getFifteenMinuteRate() {
|
||||
return meter.getFifteenMinuteRate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getFiveMinuteRate() {
|
||||
return meter.getFiveMinuteRate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMeanRate() {
|
||||
return meter.getMeanRate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getOneMinuteRate() {
|
||||
return meter.getOneMinuteRate();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.metrics.jdbc;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.eclipse.microprofile.metrics.Snapshot;
|
||||
|
||||
/**
|
||||
* Metric {@link Snapshot} wrapper for Hikari CP metric.
|
||||
*/
|
||||
public class JdbcMetricsSnapshot extends Snapshot {
|
||||
|
||||
private final com.codahale.metrics.Snapshot snapshot;
|
||||
|
||||
JdbcMetricsSnapshot(final com.codahale.metrics.Snapshot snapshot) {
|
||||
this.snapshot = snapshot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getValue(double quantile) {
|
||||
return snapshot.getValue(quantile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] getValues() {
|
||||
return snapshot.getValues();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return snapshot.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMax() {
|
||||
return snapshot.getMax();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMean() {
|
||||
return snapshot.getMean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMin() {
|
||||
return snapshot.getMin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getStdDev() {
|
||||
return snapshot.getStdDev();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(OutputStream output) {
|
||||
snapshot.dump(output);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.metrics.jdbc;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.microprofile.metrics.Snapshot;
|
||||
import org.eclipse.microprofile.metrics.Timer;
|
||||
|
||||
/**
|
||||
* {@link Timer} metric wrapper for Hikari CP metric.
|
||||
*/
|
||||
public class JdbcMetricsTimer implements Timer {
|
||||
|
||||
private final com.codahale.metrics.Timer meter;
|
||||
|
||||
JdbcMetricsTimer(final com.codahale.metrics.Timer meter) {
|
||||
this.meter = meter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(long duration, TimeUnit unit) {
|
||||
meter.update(duration, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T time(Callable<T> event) throws Exception {
|
||||
return meter.time(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void time(Runnable event) {
|
||||
meter.time(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context time() {
|
||||
return new JdbcMetricsTimerContext(meter.time());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCount() {
|
||||
return meter.getCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getFifteenMinuteRate() {
|
||||
return meter.getFifteenMinuteRate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getFiveMinuteRate() {
|
||||
return meter.getFiveMinuteRate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMeanRate() {
|
||||
return meter.getMeanRate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getOneMinuteRate() {
|
||||
return meter.getOneMinuteRate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Snapshot getSnapshot() {
|
||||
return new JdbcMetricsSnapshot(meter.getSnapshot());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.metrics.jdbc;
|
||||
|
||||
import org.eclipse.microprofile.metrics.Timer;
|
||||
|
||||
/**
|
||||
* Metric {@link Timer.Context} wrapper for Hikari CP metric.
|
||||
*/
|
||||
public class JdbcMetricsTimerContext implements Timer.Context {
|
||||
|
||||
private final com.codahale.metrics.Timer.Context context;
|
||||
|
||||
JdbcMetricsTimerContext(final com.codahale.metrics.Timer.Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long stop() {
|
||||
return context.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
context.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
/**
|
||||
* Metrics support for Helidon DB JDBC Client.
|
||||
*/
|
||||
package io.helidon.dbclient.metrics.jdbc;
|
||||
35
dbclient/metrics-jdbc/src/main/java/module-info.java
Normal file
35
dbclient/metrics-jdbc/src/main/java/module-info.java
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
import io.helidon.dbclient.jdbc.spi.HikariCpExtensionProvider;
|
||||
|
||||
/**
|
||||
* Helidon JDBC DB Client Metrics.
|
||||
*/
|
||||
module io.helidon.dbclient.metrics.jdbc {
|
||||
requires java.logging;
|
||||
requires io.helidon.dbclient;
|
||||
requires io.helidon.dbclient.jdbc;
|
||||
requires io.helidon.metrics;
|
||||
requires io.helidon.dbclient.metrics;
|
||||
requires com.zaxxer.hikari;
|
||||
requires com.codahale.metrics;
|
||||
|
||||
exports io.helidon.dbclient.metrics.jdbc;
|
||||
|
||||
provides HikariCpExtensionProvider with io.helidon.dbclient.metrics.jdbc.JdbcMetricsExtensionProvider;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#
|
||||
# Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
#
|
||||
# Licensed 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.
|
||||
#
|
||||
|
||||
io.helidon.dbclient.metrics.jdbc.JdbcMetricsExtensionProvider
|
||||
43
dbclient/metrics/pom.xml
Normal file
43
dbclient/metrics/pom.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
Licensed 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient-project</artifactId>
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>helidon-dbclient-metrics</artifactId>
|
||||
<name>Helidon DB Client Metrics</name>
|
||||
|
||||
<description>Metrics support for Helidon DB</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.helidon.dbclient</groupId>
|
||||
<artifactId>helidon-dbclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.metrics</groupId>
|
||||
<artifactId>helidon-metrics</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.metrics;
|
||||
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
|
||||
import org.eclipse.microprofile.metrics.Counter;
|
||||
import org.eclipse.microprofile.metrics.Metadata;
|
||||
import org.eclipse.microprofile.metrics.MetricRegistry;
|
||||
import org.eclipse.microprofile.metrics.MetricType;
|
||||
|
||||
/**
|
||||
* Counter metric for Helidon DB. This class implements the {@link io.helidon.dbclient.DbInterceptor} and
|
||||
* can be configured either through a {@link io.helidon.dbclient.DbClient.Builder} or through configuration.
|
||||
*/
|
||||
public final class DbCounter extends DbMetric<Counter> {
|
||||
private DbCounter(Builder builder) {
|
||||
super(builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a counter from configuration.
|
||||
*
|
||||
* @param config configuration to read
|
||||
* @return a new counter
|
||||
* @see io.helidon.dbclient.metrics.DbMetricBuilder#config(io.helidon.config.Config)
|
||||
*/
|
||||
public static DbCounter create(Config config) {
|
||||
return builder().config(config).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new counter using default configuration.
|
||||
* <p>By default the name format is {@code db.counter.statement-name}, where {@code statement-name}
|
||||
* is provided at runtime.
|
||||
*
|
||||
* @return a new counter
|
||||
*/
|
||||
public static DbCounter create() {
|
||||
return builder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new fluent API builder to create a new counter metric.
|
||||
* @return a new builder instance
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void executeMetric(Counter metric, CompletionStage<Void> aFuture) {
|
||||
aFuture
|
||||
.thenAccept(nothing -> {
|
||||
if (measureSuccess()) {
|
||||
metric.inc();
|
||||
}
|
||||
})
|
||||
.exceptionally(throwable -> {
|
||||
if (measureErrors()) {
|
||||
metric.inc();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MetricType metricType() {
|
||||
return MetricType.COUNTER;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Counter metric(MetricRegistry registry, Metadata meta) {
|
||||
return registry.counter(meta);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String defaultNamePrefix() {
|
||||
return "db.counter.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent API builder for {@link io.helidon.dbclient.metrics.DbCounter}.
|
||||
*/
|
||||
public static class Builder extends DbMetricBuilder<Builder> implements io.helidon.common.Builder<DbCounter> {
|
||||
@Override
|
||||
public DbCounter build() {
|
||||
return new DbCounter(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.metrics;
|
||||
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
|
||||
import org.eclipse.microprofile.metrics.Metadata;
|
||||
import org.eclipse.microprofile.metrics.Meter;
|
||||
import org.eclipse.microprofile.metrics.MetricRegistry;
|
||||
import org.eclipse.microprofile.metrics.MetricType;
|
||||
|
||||
/**
|
||||
* Meter for Helidon DB. This class implements the {@link io.helidon.dbclient.DbInterceptor} and
|
||||
* can be configured either through a {@link io.helidon.dbclient.DbClient.Builder} or through configuration.
|
||||
*/
|
||||
public final class DbMeter extends DbMetric<Meter> {
|
||||
private DbMeter(Builder builder) {
|
||||
super(builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a meter from configuration.
|
||||
*
|
||||
* @param config configuration to read
|
||||
* @return a new meter
|
||||
* @see io.helidon.dbclient.metrics.DbMetricBuilder#config(io.helidon.config.Config)
|
||||
*/
|
||||
public static DbMeter create(Config config) {
|
||||
return builder().config(config).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new meter using default configuration.
|
||||
* <p>By default the name format is {@code db.meter.statement-name}, where {@code statement-name}
|
||||
* is provided at runtime.
|
||||
*
|
||||
* @return a new meter
|
||||
*/
|
||||
public static DbMeter create() {
|
||||
return builder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new fluent API builder to create a new meter metric.
|
||||
* @return a new builder instance
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void executeMetric(Meter metric, CompletionStage<Void> aFuture) {
|
||||
aFuture
|
||||
.thenAccept(nothing -> {
|
||||
if (measureSuccess()) {
|
||||
metric.mark();
|
||||
}
|
||||
})
|
||||
.exceptionally(throwable -> {
|
||||
if (measureErrors()) {
|
||||
metric.mark();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MetricType metricType() {
|
||||
return MetricType.COUNTER;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Meter metric(MetricRegistry registry, Metadata meta) {
|
||||
return registry.meter(meta);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String defaultNamePrefix() {
|
||||
return "db.meter.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent API builder for {@link io.helidon.dbclient.metrics.DbMeter}.
|
||||
*/
|
||||
public static class Builder extends DbMetricBuilder<Builder> implements io.helidon.common.Builder<DbMeter> {
|
||||
@Override
|
||||
public DbMeter build() {
|
||||
return new DbMeter(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed 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 io.helidon.dbclient.metrics;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import io.helidon.dbclient.DbInterceptor;
|
||||
import io.helidon.dbclient.DbInterceptorContext;
|
||||
import io.helidon.dbclient.DbStatementType;
|
||||
import io.helidon.metrics.RegistryFactory;
|
||||
|
||||
import org.eclipse.microprofile.metrics.Metadata;
|
||||
import org.eclipse.microprofile.metrics.MetadataBuilder;
|
||||
import org.eclipse.microprofile.metrics.Metric;
|
||||
import org.eclipse.microprofile.metrics.MetricRegistry;
|
||||
import org.eclipse.microprofile.metrics.MetricType;
|
||||
|
||||
/**
|
||||
* Common ancestor for Helidon DB metrics.
|
||||
*/
|
||||
abstract class DbMetric<T extends Metric> implements DbInterceptor {
|
||||
private final Metadata meta;
|
||||
private final String description;
|
||||
private final BiFunction<String, DbStatementType, String> nameFunction;
|
||||
private final MetricRegistry registry;
|
||||
private final ConcurrentHashMap<String, T> cache = new ConcurrentHashMap<>();
|
||||
private final boolean measureErrors;
|
||||
private final boolean measureSuccess;
|
||||
|
||||
protected DbMetric(DbMetricBuilder<?> builder) {
|
||||
BiFunction<String, DbStatementType, String> namedFunction = builder.nameFormat();
|
||||
this.meta = builder.meta();
|
||||
|
||||
if (null == namedFunction) {
|
||||
namedFunction = (name, statement) -> defaultNamePrefix() + name;
|
||||
}
|
||||
this.nameFunction = namedFunction;
|
||||
this.registry = RegistryFactory.getInstance().getRegistry(MetricRegistry.Type.APPLICATION);
|
||||
this.measureErrors = builder.measureErrors();
|
||||
this.measureSuccess = builder.measureSuccess();
|
||||
String tmpDescription;
|
||||
if (builder.description() == null) {
|
||||
tmpDescription = ((null == meta) ? null : meta.getDescription().orElse(null));
|
||||
} else {
|
||||
tmpDescription = builder.description();
|
||||
}
|
||||
this.description = tmpDescription;
|
||||
}
|
||||
|
||||
protected abstract String defaultNamePrefix();
|
||||
|
||||
@Override
|
||||
public CompletableFuture<DbInterceptorContext> statement(DbInterceptorContext interceptorContext) {
|
||||
DbStatementType dbStatementType = interceptorContext.statementType();
|
||||
String statementName = interceptorContext.statementName();
|
||||
|
||||
T metric = cache.computeIfAbsent(statementName, s -> {
|
||||
String name = nameFunction.apply(statementName, dbStatementType);
|
||||
MetadataBuilder builder = (meta == null)
|
||||
? Metadata.builder().withName(name).withType(metricType())
|
||||
: Metadata.builder(meta);
|
||||
if (description != null) {
|
||||
builder = builder.withDescription(description);
|
||||
}
|
||||
return metric(registry, builder.build());
|
||||
});
|
||||
|
||||
executeMetric(metric, interceptorContext.statementFuture());
|
||||
|
||||
return CompletableFuture.completedFuture(interceptorContext);
|
||||
}
|
||||
|
||||
protected boolean measureErrors() {
|
||||
return measureErrors;
|
||||
}
|
||||
|
||||
protected boolean measureSuccess() {
|
||||
return measureSuccess;
|
||||
}
|
||||
|
||||
protected abstract void executeMetric(T metric, CompletionStage<Void> aFuture);
|
||||
protected abstract MetricType metricType();
|
||||
protected abstract T metric(MetricRegistry registry, Metadata meta);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user