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 <sru...@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>

Reply via email to