You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by sr...@apache.org on 2018/10/25 15:08:38 UTC
[incubator-plc4x] branch master updated: [plc4j-pool] initial
implementation of connection pool using commons-pool
This is an automated email from the ASF dual-hosted git repository.
sruehl pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git
The following commit(s) were added to refs/heads/master by this push:
new c29fd35 [plc4j-pool] initial implementation of connection pool using commons-pool
c29fd35 is described below
commit c29fd3597572c1153236becb5a1e8f42c3584c16
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Thu Oct 25 14:49:54 2018 +0200
[plc4j-pool] initial implementation of connection pool using commons-pool
---
.../PlcUsernamePasswordAuthentication.java | 21 ++
.../org/apache/plc4x/java/PlcDriverManager.java | 5 +-
plc4j/utils/{ => connection-pool}/pom.xml | 42 ++--
.../connectionpool/PooledPlcConnectionFactory.java | 46 ++++
.../connectionpool/PooledPlcDriverManager.java | 171 +++++++++++++++
.../WrappedPooledConnectionException.java | 33 +++
.../utils/connectionpool/PooledDummyDriver.java | 51 +++++
.../connectionpool/PooledPlcDriverManagerTest.java | 242 +++++++++++++++++++++
.../services/org.apache.plc4x.java.spi.PlcDriver | 19 ++
.../connection-pool/src/test/resources/logback.xml | 34 +++
plc4j/utils/pom.xml | 1 +
11 files changed, 651 insertions(+), 14 deletions(-)
diff --git a/plc4j/api/src/main/java/org/apache/plc4x/java/api/authentication/PlcUsernamePasswordAuthentication.java b/plc4j/api/src/main/java/org/apache/plc4x/java/api/authentication/PlcUsernamePasswordAuthentication.java
index 5a0f383..dfb6ff6 100644
--- a/plc4j/api/src/main/java/org/apache/plc4x/java/api/authentication/PlcUsernamePasswordAuthentication.java
+++ b/plc4j/api/src/main/java/org/apache/plc4x/java/api/authentication/PlcUsernamePasswordAuthentication.java
@@ -40,4 +40,25 @@ public class PlcUsernamePasswordAuthentication implements PlcAuthentication {
return password;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PlcUsernamePasswordAuthentication that = (PlcUsernamePasswordAuthentication) o;
+ return Objects.equals(username, that.username) &&
+ Objects.equals(password, that.password);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(username, password);
+ }
+
+ @Override
+ public String toString() {
+ return "PlcUsernamePasswordAuthentication{" +
+ "username='" + username + '\'' +
+ ", password='" + "*****************" + '\'' +
+ '}';
+ }
}
diff --git a/plc4j/core/src/main/java/org/apache/plc4x/java/PlcDriverManager.java b/plc4j/core/src/main/java/org/apache/plc4x/java/PlcDriverManager.java
index 3b50927..c98f9ec 100644
--- a/plc4j/core/src/main/java/org/apache/plc4x/java/PlcDriverManager.java
+++ b/plc4j/core/src/main/java/org/apache/plc4x/java/PlcDriverManager.java
@@ -31,13 +31,16 @@ import java.util.ServiceLoader;
public class PlcDriverManager {
- private Map<String, PlcDriver> driverMap = null;
+ protected ClassLoader classLoader;
+
+ private Map<String, PlcDriver> driverMap;
public PlcDriverManager() {
this(Thread.currentThread().getContextClassLoader());
}
public PlcDriverManager(ClassLoader classLoader) {
+ this.classLoader = classLoader;
driverMap = new HashMap<>();
ServiceLoader<PlcDriver> plcDriverLoader = ServiceLoader.load(PlcDriver.class, classLoader);
for (PlcDriver driver : plcDriverLoader) {
diff --git a/plc4j/utils/pom.xml b/plc4j/utils/connection-pool/pom.xml
similarity index 50%
copy from plc4j/utils/pom.xml
copy to plc4j/utils/connection-pool/pom.xml
index 85f83c6..2822228 100644
--- a/plc4j/utils/pom.xml
+++ b/plc4j/utils/connection-pool/pom.xml
@@ -17,26 +17,42 @@
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">
+<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>plc4j-utils</artifactId>
<groupId>org.apache.plc4x</groupId>
- <artifactId>plc4j</artifactId>
<version>0.2.0-SNAPSHOT</version>
</parent>
- <artifactId>plc4j-utils</artifactId>
- <packaging>pom</packaging>
-
- <name>PLC4J: Utils</name>
- <description>A collection of utilities used in multiple modules.</description>
-
- <modules>
- <module>raw-sockets</module>
- <module>test-utils</module>
- <module>wireshark-utils</module>
- </modules>
+ <artifactId>plc4j-connection-pool</artifactId>
+
+ <name>PLC4J: Utils: Connection Pool</name>
+ <description>An implementation of a connection pool based on Apache Commons Pool.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.plc4x</groupId>
+ <artifactId>plc4j-api</artifactId>
+ <version>0.2.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.plc4x</groupId>
+ <artifactId>plc4j-core</artifactId>
+ <version>0.2.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-pool2</artifactId>
+ <version>2.6.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
+ </dependencies>
</project>
\ No newline at end of file
diff --git a/plc4j/utils/connection-pool/src/main/java/org/apache/plc4x/java/utils/connectionpool/PooledPlcConnectionFactory.java b/plc4j/utils/connection-pool/src/main/java/org/apache/plc4x/java/utils/connectionpool/PooledPlcConnectionFactory.java
new file mode 100644
index 0000000..3563c4c
--- /dev/null
+++ b/plc4j/utils/connection-pool/src/main/java/org/apache/plc4x/java/utils/connectionpool/PooledPlcConnectionFactory.java
@@ -0,0 +1,46 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+package org.apache.plc4x.java.utils.connectionpool;
+
+import org.apache.commons.pool2.BasePooledObjectFactory;
+import org.apache.commons.pool2.PooledObject;
+import org.apache.commons.pool2.impl.DefaultPooledObject;
+import org.apache.plc4x.java.api.PlcConnection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class PooledPlcConnectionFactory extends BasePooledObjectFactory<PlcConnection> {
+
+ private final static Logger LOGGER = LoggerFactory.getLogger(PooledPlcConnectionFactory.class);
+
+ @Override
+ public PooledObject<PlcConnection> wrap(PlcConnection plcConnection) {
+ LOGGER.debug("Wrapping connection {}", plcConnection);
+ return new DefaultPooledObject<>(plcConnection);
+ }
+
+ @Override
+ public void destroyObject(PooledObject<PlcConnection> p) throws Exception {
+ p.getObject().close();
+ }
+
+ @Override
+ public boolean validateObject(PooledObject<PlcConnection> p) {
+ return p.getObject().isConnected();
+ }
+}
diff --git a/plc4j/utils/connection-pool/src/main/java/org/apache/plc4x/java/utils/connectionpool/PooledPlcDriverManager.java b/plc4j/utils/connection-pool/src/main/java/org/apache/plc4x/java/utils/connectionpool/PooledPlcDriverManager.java
new file mode 100644
index 0000000..fb4db02
--- /dev/null
+++ b/plc4j/utils/connection-pool/src/main/java/org/apache/plc4x/java/utils/connectionpool/PooledPlcDriverManager.java
@@ -0,0 +1,171 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+package org.apache.plc4x.java.utils.connectionpool;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.pool2.ObjectPool;
+import org.apache.commons.pool2.impl.GenericObjectPool;
+import org.apache.plc4x.java.PlcDriverManager;
+import org.apache.plc4x.java.api.PlcConnection;
+import org.apache.plc4x.java.api.authentication.PlcAuthentication;
+import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Proxy;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+public class PooledPlcDriverManager extends PlcDriverManager {
+
+ private final static Logger LOGGER = LoggerFactory.getLogger(PooledPlcDriverManager.class);
+
+ private PoolCreator poolCreator;
+
+ private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
+
+ private ConcurrentMap<Pair<String, PlcAuthentication>, ObjectPool<PlcConnection>> poolMap = new ConcurrentHashMap<>();
+
+ // Marker class do detected a non null value
+ private static final NoPlcAuthentication noPlcAuthentication = new NoPlcAuthentication();
+
+ public PooledPlcDriverManager() {
+ this(GenericObjectPool::new);
+ }
+
+ public PooledPlcDriverManager(ClassLoader classLoader) {
+ super(classLoader);
+ this.poolCreator = GenericObjectPool::new;
+ }
+
+ public PooledPlcDriverManager(PoolCreator poolCreator) {
+ this.poolCreator = poolCreator;
+ }
+
+ public PooledPlcDriverManager(ClassLoader classLoader, PoolCreator poolCreator) {
+ super(classLoader);
+ this.poolCreator = poolCreator;
+ }
+
+ @Override
+ public PlcConnection getConnection(String url) throws PlcConnectionException {
+ return getConnection(url, noPlcAuthentication);
+ }
+
+ @Override
+ public PlcConnection getConnection(String url, PlcAuthentication authentication) throws PlcConnectionException {
+ Pair<String, PlcAuthentication> argPair = Pair.of(url, authentication);
+ ObjectPool<PlcConnection> pool = retrieveFromPool(argPair);
+ try {
+ LOGGER.debug("Try to borrow an object for url {} and authentication {}", url, authentication);
+ PlcConnection plcConnection = pool.borrowObject();
+ return (PlcConnection) Proxy.newProxyInstance(classLoader, new Class[]{PlcConnection.class}, (o, method, objects) -> {
+ if (method.getName().equals("close")) {
+ LOGGER.debug("close called on {}. Returning to {}", plcConnection, pool);
+ pool.returnObject(plcConnection);
+ return null;
+ } else {
+ return method.invoke(plcConnection, objects);
+ }
+ });
+ } catch (Exception e) {
+ throw new PlcConnectionException(e);
+ }
+ }
+
+ private ObjectPool<PlcConnection> retrieveFromPool(Pair<String, PlcAuthentication> argPair) {
+ String url = argPair.getLeft();
+ PlcAuthentication plcAuthentication = argPair.getRight();
+ ObjectPool<PlcConnection> pool = poolMap.get(argPair);
+ if (pool == null) {
+ Lock lock = readWriteLock.readLock();
+ lock.lock();
+ try {
+ poolMap.computeIfAbsent(argPair, pair -> poolCreator.createPool(new PooledPlcConnectionFactory() {
+ @Override
+ public PlcConnection create() throws PlcConnectionException {
+ if (plcAuthentication == noPlcAuthentication) {
+ LOGGER.debug("getting actual connection for {}", url);
+ return PooledPlcDriverManager.super.getConnection(url);
+ } else {
+ LOGGER.debug("getting actual connection for {} and plcAuthentication {}", url, plcAuthentication);
+ return PooledPlcDriverManager.super.getConnection(url, plcAuthentication);
+ }
+ }
+ }));
+ pool = poolMap.get(argPair);
+ } finally {
+ lock.unlock();
+ }
+ }
+ return pool;
+ }
+
+ @FunctionalInterface
+ interface PoolCreator {
+ ObjectPool<PlcConnection> createPool(PooledPlcConnectionFactory pooledPlcConnectionFactory);
+ }
+
+ // TODO: maybe add a Thread which calls this cyclic
+ public void removedUnusedPools() {
+ Lock lock = readWriteLock.writeLock();
+ lock.lock();
+ try {
+ Set<Pair<String, PlcAuthentication>> itemsToBeremoved = new LinkedHashSet<>();
+ poolMap.forEach((key, value) -> {
+ // TODO: check if this pool has been used in the last time and if not remove it.
+ // TODO: evicting empty pools for now
+ if (value.getNumActive() == 0 && value.getNumIdle() == 0) {
+ LOGGER.info("Removing unused pool {}", value);
+ itemsToBeremoved.add(key);
+ }
+ });
+ itemsToBeremoved.forEach(poolMap::remove);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ // TODO: maybe export to jmx
+ public Map<String, Number> getStatistics() {
+ HashMap<String, Number> statistics = new HashMap<>();
+ for (Map.Entry<Pair<String, PlcAuthentication>, ObjectPool<PlcConnection>> poolEntry : poolMap.entrySet()) {
+ Pair<String, PlcAuthentication> pair = poolEntry.getKey();
+ ObjectPool<PlcConnection> objectPool = poolEntry.getValue();
+ String url = pair.getLeft();
+ PlcAuthentication plcAuthentication = pair.getRight();
+
+ String authSuffix = plcAuthentication != noPlcAuthentication ? "/" + plcAuthentication : "";
+ statistics.put(url + authSuffix + ".numActive", objectPool.getNumActive());
+ statistics.put(url + authSuffix + ".numIdle", objectPool.getNumIdle());
+ }
+
+ return statistics;
+ }
+
+ private static final class NoPlcAuthentication implements PlcAuthentication {
+
+ }
+}
diff --git a/plc4j/utils/connection-pool/src/main/java/org/apache/plc4x/java/utils/connectionpool/WrappedPooledConnectionException.java b/plc4j/utils/connection-pool/src/main/java/org/apache/plc4x/java/utils/connectionpool/WrappedPooledConnectionException.java
new file mode 100644
index 0000000..079c228
--- /dev/null
+++ b/plc4j/utils/connection-pool/src/main/java/org/apache/plc4x/java/utils/connectionpool/WrappedPooledConnectionException.java
@@ -0,0 +1,33 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+package org.apache.plc4x.java.utils.connectionpool;
+
+import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
+
+public class WrappedPooledConnectionException extends RuntimeException {
+
+ private final PlcConnectionException innerException;
+
+ public WrappedPooledConnectionException(PlcConnectionException innerException) {
+ this.innerException = innerException;
+ }
+
+ public PlcConnectionException getInnerException() {
+ return innerException;
+ }
+}
diff --git a/plc4j/utils/connection-pool/src/test/java/org/apache/plc4x/java/utils/connectionpool/PooledDummyDriver.java b/plc4j/utils/connection-pool/src/test/java/org/apache/plc4x/java/utils/connectionpool/PooledDummyDriver.java
new file mode 100644
index 0000000..4cb0b0f
--- /dev/null
+++ b/plc4j/utils/connection-pool/src/test/java/org/apache/plc4x/java/utils/connectionpool/PooledDummyDriver.java
@@ -0,0 +1,51 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+package org.apache.plc4x.java.utils.connectionpool;
+
+import org.apache.plc4x.java.api.PlcConnection;
+import org.apache.plc4x.java.api.authentication.PlcAuthentication;
+import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
+import org.apache.plc4x.java.spi.PlcDriver;
+
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+
+public class PooledDummyDriver implements PlcDriver {
+
+ private PlcDriver mockedPlcDriver = mock(PlcDriver.class, RETURNS_DEEP_STUBS);
+
+ @Override
+ public String getProtocolCode() {
+ return PooledDummyDriver.class.getName();
+ }
+
+ @Override
+ public String getProtocolName() {
+ return mockedPlcDriver.getProtocolCode();
+ }
+
+ @Override
+ public PlcConnection connect(String url) throws PlcConnectionException {
+ return mockedPlcDriver.connect(url);
+ }
+
+ @Override
+ public PlcConnection connect(String url, PlcAuthentication authentication) throws PlcConnectionException {
+ return mockedPlcDriver.connect(url, authentication);
+ }
+}
diff --git a/plc4j/utils/connection-pool/src/test/java/org/apache/plc4x/java/utils/connectionpool/PooledPlcDriverManagerTest.java b/plc4j/utils/connection-pool/src/test/java/org/apache/plc4x/java/utils/connectionpool/PooledPlcDriverManagerTest.java
new file mode 100644
index 0000000..30dcf73
--- /dev/null
+++ b/plc4j/utils/connection-pool/src/test/java/org/apache/plc4x/java/utils/connectionpool/PooledPlcDriverManagerTest.java
@@ -0,0 +1,242 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+package org.apache.plc4x.java.utils.connectionpool;
+
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.commons.pool2.impl.GenericObjectPool;
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.apache.plc4x.java.api.PlcConnection;
+import org.apache.plc4x.java.api.authentication.PlcAuthentication;
+import org.apache.plc4x.java.api.authentication.PlcUsernamePasswordAuthentication;
+import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
+import org.apache.plc4x.java.api.messages.PlcReadRequest;
+import org.apache.plc4x.java.api.messages.PlcSubscriptionRequest;
+import org.apache.plc4x.java.api.messages.PlcUnsubscriptionRequest;
+import org.apache.plc4x.java.api.messages.PlcWriteRequest;
+import org.apache.plc4x.java.spi.PlcDriver;
+import org.assertj.core.api.WithAssertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.*;
+import java.util.stream.IntStream;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class PooledPlcDriverManagerTest implements WithAssertions {
+
+ private PooledPlcDriverManager SUT = new PooledPlcDriverManager(pooledPlcConnectionFactory -> {
+ GenericObjectPoolConfig<PlcConnection> plcConnectionGenericObjectPoolConfig = new GenericObjectPoolConfig<>();
+ plcConnectionGenericObjectPoolConfig.setMinIdle(1);
+ return new GenericObjectPool<>(pooledPlcConnectionFactory, plcConnectionGenericObjectPoolConfig);
+ });
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ PlcDriver plcDriver;
+
+ private ExecutorService executorService;
+
+ @SuppressWarnings("unchecked")
+ @BeforeEach
+ void setUp() throws Exception {
+ Map<String, PlcDriver> driverMap = (Map) FieldUtils.getField(PooledPlcDriverManager.class, "driverMap", true).get(SUT);
+ driverMap.put("dummydummy", plcDriver);
+ executorService = Executors.newFixedThreadPool(100);
+
+ assertThat(SUT.getStatistics()).isEmpty();
+ }
+
+ @AfterEach
+ void tearDown() {
+ executorService.shutdown();
+ }
+
+ @Test
+ void getConnection() throws Exception {
+ when(plcDriver.connect(anyString())).then(invocationOnMock -> new DummyPlcConnection(invocationOnMock.getArgument(0)));
+
+ LinkedList<Callable<PlcConnection>> callables = new LinkedList<>();
+
+ // This: should result in one open connection
+ IntStream.range(0, 8).forEach(i -> callables.add(() -> {
+ try {
+ return SUT.getConnection("dummydummy:single");
+ } catch (PlcConnectionException e) {
+ throw new RuntimeException(e);
+ }
+ }));
+
+ // This should result in five open connections
+ IntStream.range(0, 5).forEach(i -> callables.add(() -> {
+ try {
+ return SUT.getConnection("dummydummy:multi-" + i);
+ } catch (PlcConnectionException e) {
+ throw new RuntimeException(e);
+ }
+ }));
+
+ List<Future<PlcConnection>> futures = executorService.invokeAll(callables);
+
+ // As we have a pool size of 8 we should have only 8 + 5 calls for the separate pools
+ verify(plcDriver, times(13)).connect(anyString());
+
+ assertThat(SUT.getStatistics()).contains(
+ entry("dummydummy:single.numActive", 8),
+ entry("dummydummy:single.numIdle", 0)
+ );
+
+ futures.forEach(plcConnectionFuture -> {
+ try {
+ plcConnectionFuture.get().close();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ assertThat(SUT.getStatistics()).contains(
+ entry("dummydummy:single.numActive", 0),
+ entry("dummydummy:single.numIdle", 8)
+ );
+ }
+
+ @Test
+ void getConnectionWithAuth() throws Exception {
+ when(plcDriver.connect(anyString(), any())).then(invocationOnMock -> new DummyPlcConnection(invocationOnMock.getArgument(0), invocationOnMock.getArgument(1)));
+
+ LinkedList<Callable<PlcConnection>> callables = new LinkedList<>();
+
+ // This: should result in one open connection
+ IntStream.range(0, 8).forEach(i -> callables.add(() -> {
+ try {
+ return SUT.getConnection("dummydummy:single", new PlcUsernamePasswordAuthentication("user", "passwordp954368564098ß"));
+ } catch (PlcConnectionException e) {
+ throw new RuntimeException(e);
+ }
+ }));
+
+ // This should result in five open connections
+ IntStream.range(0, 5).forEach(i -> callables.add(() -> {
+ try {
+ return SUT.getConnection("dummydummy:single-" + i, new PlcUsernamePasswordAuthentication("user", "passwordp954368564098ß"));
+ } catch (PlcConnectionException e) {
+ throw new RuntimeException(e);
+ }
+ }));
+
+ List<Future<PlcConnection>> futures = executorService.invokeAll(callables);
+
+ // As we have a pool size of 8 we should have only 8 + 5 calls for the separate pools
+ verify(plcDriver, times(13)).connect(anyString(), any());
+
+ assertThat(SUT.getStatistics()).contains(
+ entry("dummydummy:single/PlcUsernamePasswordAuthentication{username='user', password='*****************'}.numActive", 8),
+ entry("dummydummy:single/PlcUsernamePasswordAuthentication{username='user', password='*****************'}.numIdle", 0)
+ );
+
+ futures.forEach(plcConnectionFuture -> {
+ try {
+ plcConnectionFuture.get().connect();
+ plcConnectionFuture.get().close();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ assertThat(SUT.getStatistics()).contains(
+ entry("dummydummy:single/PlcUsernamePasswordAuthentication{username='user', password='*****************'}.numActive", 0),
+ entry("dummydummy:single/PlcUsernamePasswordAuthentication{username='user', password='*****************'}.numIdle", 8)
+ );
+ }
+
+ class DummyPlcConnection implements PlcConnection {
+
+ private final String url;
+
+ private final PlcAuthentication plcAuthentication;
+
+ boolean connected = false;
+
+ public DummyPlcConnection(String url) {
+ this(url, null);
+ }
+
+ public DummyPlcConnection(String url, PlcAuthentication plcAuthentication) {
+ this.url = url;
+ this.plcAuthentication = plcAuthentication;
+ }
+
+ @Override
+ public void connect() throws PlcConnectionException {
+ connected = true;
+ }
+
+ @Override
+ public boolean isConnected() {
+ return connected;
+ }
+
+ @Override
+ public void close() throws Exception {
+ throw new UnsupportedOperationException("this should never be called due to pool");
+ }
+
+ @Override
+ public Optional<PlcReadRequest.Builder> readRequestBuilder() {
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional<PlcWriteRequest.Builder> writeRequestBuilder() {
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional<PlcSubscriptionRequest.Builder> subscriptionRequestBuilder() {
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional<PlcUnsubscriptionRequest.Builder> unsubscriptionRequestBuilder() {
+ return Optional.empty();
+ }
+
+ @Override
+ public String toString() {
+ return "DummyPlcConnection{" +
+ "url='" + url + '\'' +
+ ", plcAuthentication=" + plcAuthentication +
+ ", connected=" + connected +
+ '}';
+ }
+ }
+}
\ No newline at end of file
diff --git a/plc4j/utils/connection-pool/src/test/resources/META-INF/services/org.apache.plc4x.java.spi.PlcDriver b/plc4j/utils/connection-pool/src/test/resources/META-INF/services/org.apache.plc4x.java.spi.PlcDriver
new file mode 100644
index 0000000..a2d97ed
--- /dev/null
+++ b/plc4j/utils/connection-pool/src/test/resources/META-INF/services/org.apache.plc4x.java.spi.PlcDriver
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+org.apache.plc4x.java.utils.connectionpool.PooledDummyDriver
diff --git a/plc4j/utils/connection-pool/src/test/resources/logback.xml b/plc4j/utils/connection-pool/src/test/resources/logback.xml
new file mode 100644
index 0000000..31c49f0
--- /dev/null
+++ b/plc4j/utils/connection-pool/src/test/resources/logback.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<configuration xmlns="http://ch.qos.logback/xml/ns/logback"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback https://raw.githubusercontent.com/enricopulatzo/logback-XSD/master/src/main/xsd/logback.xsd">
+
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <!-- encoders are assigned the type
+ ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
+ <encoder>
+ <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <root level="DEBUG">
+ <appender-ref ref="STDOUT" />
+ </root>
+
+</configuration>
\ No newline at end of file
diff --git a/plc4j/utils/pom.xml b/plc4j/utils/pom.xml
index 85f83c6..c7dad25 100644
--- a/plc4j/utils/pom.xml
+++ b/plc4j/utils/pom.xml
@@ -34,6 +34,7 @@
<description>A collection of utilities used in multiple modules.</description>
<modules>
+ <module>connection-pool</module>
<module>raw-sockets</module>
<module>test-utils</module>
<module>wireshark-utils</module>