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>