Skip to content

Commit c1e87ab

Browse files
authored
Helidon DB Client (helidon-io#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>
1 parent 115a822 commit c1e87ab

217 files changed

Lines changed: 24370 additions & 21 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bom/pom.xml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,75 @@
503503
<artifactId>helidon-common-metrics</artifactId>
504504
<version>${helidon.version}</version>
505505
</dependency>
506+
<dependency>
507+
<groupId>io.helidon.common</groupId>
508+
<artifactId>helidon-common-mapper</artifactId>
509+
<version>${project.version}</version>
510+
</dependency>
511+
512+
<!-- db client -->
513+
<dependency>
514+
<groupId>io.helidon.dbclient</groupId>
515+
<artifactId>helidon-dbclient</artifactId>
516+
<version>${project.version}</version>
517+
</dependency>
518+
<dependency>
519+
<groupId>io.helidon.dbclient</groupId>
520+
<artifactId>helidon-dbclient-common</artifactId>
521+
<version>${project.version}</version>
522+
</dependency>
523+
<dependency>
524+
<groupId>io.helidon.dbclient</groupId>
525+
<artifactId>helidon-dbclient-jdbc</artifactId>
526+
<version>${project.version}</version>
527+
</dependency>
528+
<dependency>
529+
<groupId>io.helidon.dbclient</groupId>
530+
<artifactId>helidon-dbclient-mongodb</artifactId>
531+
<version>${project.version}</version>
532+
</dependency>
533+
<dependency>
534+
<groupId>io.helidon.dbclient</groupId>
535+
<artifactId>helidon-dbclient-mongodb</artifactId>
536+
<version>${project.version}</version>
537+
</dependency>
538+
<dependency>
539+
<groupId>io.helidon.dbclient</groupId>
540+
<artifactId>helidon-dbclient-health</artifactId>
541+
<version>${project.version}</version>
542+
</dependency>
543+
<dependency>
544+
<groupId>io.helidon.dbclient</groupId>
545+
<artifactId>helidon-dbclient-jsonp</artifactId>
546+
<version>${project.version}</version>
547+
</dependency>
548+
<dependency>
549+
<groupId>io.helidon.dbclient</groupId>
550+
<artifactId>helidon-dbclient-metrics</artifactId>
551+
<version>${project.version}</version>
552+
</dependency>
553+
<dependency>
554+
<groupId>io.helidon.dbclient</groupId>
555+
<artifactId>helidon-dbclient-metrics-jdbc</artifactId>
556+
<version>${project.version}</version>
557+
</dependency>
558+
<dependency>
559+
<groupId>io.helidon.dbclient</groupId>
560+
<artifactId>helidon-dbclient-tracing</artifactId>
561+
<version>${project.version}</version>
562+
</dependency>
563+
<dependency>
564+
<groupId>io.helidon.dbclient</groupId>
565+
<artifactId>helidon-dbclient-webserver-jsonp</artifactId>
566+
<version>${project.version}</version>
567+
</dependency>
568+
569+
<!-- db client examples -->
570+
<dependency>
571+
<groupId>io.helidon.examples.dbclient</groupId>
572+
<artifactId>helidon-examples-dbclient-common</artifactId>
573+
<version>${project.version}</version>
574+
</dependency>
506575

507576
<!-- tracing -->
508577
<dependency>
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
/*
2+
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.helidon.common.configurable;
17+
18+
import java.util.Iterator;
19+
import java.util.LinkedHashMap;
20+
import java.util.Optional;
21+
import java.util.concurrent.locks.Lock;
22+
import java.util.concurrent.locks.ReadWriteLock;
23+
import java.util.concurrent.locks.ReentrantReadWriteLock;
24+
import java.util.function.Supplier;
25+
26+
import io.helidon.config.Config;
27+
28+
/**
29+
* Least recently used cache.
30+
* This cache has a capacity. When the capacity is reached, the oldest record is removed from the cache when a new one
31+
* is added.
32+
*
33+
* @param <K> type of the keys of the map
34+
* @param <V> type of the values of the map
35+
*/
36+
public final class LruCache<K, V> {
37+
/**
38+
* Default capacity of the cache: {@value}.
39+
*/
40+
public static final int DEFAULT_CAPACITY = 10000;
41+
42+
private final LinkedHashMap<K, V> backingMap = new LinkedHashMap<>();
43+
private final ReadWriteLock rwLock = new ReentrantReadWriteLock(true);
44+
private final Lock readLock = rwLock.readLock();
45+
private final Lock writeLock = rwLock.writeLock();
46+
47+
private final int capacity;
48+
49+
private LruCache(Builder<K, V> builder) {
50+
this.capacity = builder.capacity;
51+
}
52+
53+
/**
54+
* Create a new builder.
55+
*
56+
* @param <K> key type
57+
* @param <V> value type
58+
* @return a new fluent API builder instance
59+
*/
60+
public static <K, V> Builder<K, V> builder() {
61+
return new Builder<>();
62+
}
63+
64+
/**
65+
* Create an instance with default configuration.
66+
*
67+
* @param <K> key type
68+
* @param <V> value type
69+
* @return a new cache instance
70+
* @see #DEFAULT_CAPACITY
71+
*/
72+
public static <K, V> LruCache<K, V> create() {
73+
Builder<K, V> builder = builder();
74+
return builder.build();
75+
}
76+
77+
/**
78+
* Get a value from the cache.
79+
*
80+
* @param key key to retrieve
81+
* @return value if present or empty
82+
*/
83+
public Optional<V> get(K key) {
84+
readLock.lock();
85+
86+
V value;
87+
try {
88+
value = backingMap.get(key);
89+
} finally {
90+
readLock.unlock();
91+
}
92+
93+
if (null == value) {
94+
return Optional.empty();
95+
}
96+
97+
writeLock.lock();
98+
try {
99+
// make sure the value is the last in the map (I do ignore a race here, as it is not significant)
100+
// if some other thread moved another record to the front, we just move ours before it
101+
102+
// TODO this hurts - we just need to move the key to the last position
103+
// maybe this should be replaced with a list and a map?
104+
value = backingMap.get(key);
105+
if (null == value) {
106+
return Optional.empty();
107+
}
108+
backingMap.remove(key);
109+
backingMap.put(key, value);
110+
111+
return Optional.of(value);
112+
} finally {
113+
writeLock.unlock();
114+
}
115+
}
116+
117+
/**
118+
* Remove a value from the cache.
119+
*
120+
* @param key key of the record to remove
121+
* @return the value that was mapped to the key, or empty if none was
122+
*/
123+
public Optional<V> remove(K key) {
124+
125+
writeLock.lock();
126+
try {
127+
return Optional.ofNullable(backingMap.remove(key));
128+
} finally {
129+
writeLock.unlock();
130+
}
131+
}
132+
133+
/**
134+
* Put a value to the cache.
135+
*
136+
* @param key key to add
137+
* @param value value to add
138+
* @return value that was already mapped or empty if the value was not mapped
139+
*/
140+
public Optional<V> put(K key, V value) {
141+
writeLock.lock();
142+
try {
143+
V currentValue = backingMap.remove(key);
144+
if (null == currentValue) {
145+
// need to free space - we did not make the map smaller
146+
if (backingMap.size() >= capacity) {
147+
Iterator<V> iterator = backingMap.values().iterator();
148+
iterator.next();
149+
iterator.remove();
150+
}
151+
}
152+
153+
backingMap.put(key, value);
154+
return Optional.ofNullable(currentValue);
155+
} finally {
156+
writeLock.unlock();
157+
}
158+
}
159+
160+
/**
161+
* Either return a cached value or compute it and cache it.
162+
* In case this method is called in parallel for the same key, the value actually present in the map may be from
163+
* any of the calls.
164+
* This method always returns either the existing value from the map, or the value provided by the supplier. It
165+
* never returns a result from another thread's supplier.
166+
*
167+
* @param key key to check/insert value for
168+
* @param valueSupplier supplier called if the value is not yet cached, or is invalid
169+
* @return current value from the cache, or computed value from the supplier
170+
*/
171+
public Optional<V> computeValue(K key, Supplier<Optional<V>> valueSupplier) {
172+
// get is properly synchronized
173+
Optional<V> currentValue = get(key);
174+
if (currentValue.isPresent()) {
175+
return currentValue;
176+
}
177+
Optional<V> newValue = valueSupplier.get();
178+
// put is also properly synchronized - nevertheless we may replace the value more then once
179+
// if called from parallel threads
180+
newValue.ifPresent(theValue -> put(key, theValue));
181+
182+
return newValue;
183+
}
184+
185+
/**
186+
* Current size of the map.
187+
*
188+
* @return number of records currently cached
189+
*/
190+
public int size() {
191+
readLock.lock();
192+
try {
193+
return backingMap.size();
194+
} finally {
195+
readLock.unlock();
196+
}
197+
}
198+
199+
/**
200+
* Capacity of this cache.
201+
*
202+
* @return configured capacity of this cache
203+
*/
204+
public int capacity() {
205+
return capacity;
206+
}
207+
208+
// for unit testing
209+
V directGet(K key) {
210+
return backingMap.get(key);
211+
}
212+
213+
/**
214+
* Fluent API builder for {@link io.helidon.common.configurable.LruCache}.
215+
*
216+
* @param <K> type of keys
217+
* @param <V> type of values
218+
*/
219+
public static class Builder<K, V> implements io.helidon.common.Builder<LruCache<K, V>> {
220+
private int capacity = DEFAULT_CAPACITY;
221+
222+
@Override
223+
public LruCache<K, V> build() {
224+
return new LruCache<>(this);
225+
}
226+
227+
/**
228+
* Load configuration of this cache from configuration.
229+
*
230+
* @param config configuration
231+
* @return updated builder instance
232+
*/
233+
public Builder<K, V> config(Config config) {
234+
config.get("capacity").asInt().ifPresent(this::capacity);
235+
return this;
236+
}
237+
238+
/**
239+
* Configure capacity of the cache.
240+
*
241+
* @param capacity maximal number of records in the cache before the oldest one is removed
242+
* @return updated builder instance
243+
*/
244+
public Builder<K, V> capacity(int capacity) {
245+
this.capacity = capacity;
246+
return this;
247+
}
248+
}
249+
}

0 commit comments

Comments
 (0)