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/11/24 12:12:36 UTC
[incubator-plc4x] branch develop updated: [plc4j-opm] enable write
support (PLC4X-70)
This is an automated email from the ASF dual-hosted git repository.
sruehl pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git
The following commit(s) were added to refs/heads/develop by this push:
new 6fa99c2 [plc4j-opm] enable write support (PLC4X-70)
6fa99c2 is described below
commit 6fa99c232b7c810d8e009eb951387fc025de636a
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Sat Nov 24 13:04:10 2018 +0100
[plc4j-opm] enable write support (PLC4X-70)
---
.../org/apache/plc4x/java/mock/MockDevice.java | 19 +-
.../java/org/apache/plc4x/java/mock/MockField.java | 13 +
.../apache/plc4x/java/mock/MockFieldHandler.java | 70 +++++
.../org/apache/plc4x/java/mock/MockFieldItem.java} | 25 +-
.../apache/plc4x/java/mock/PlcMockConnection.java | 94 ++++--
.../plc4x/java/opm/PlcEntityInterceptor.java | 247 +++++++++++++---
.../apache/plc4x/java/opm/PlcEntityManager.java | 114 ++++----
.../java/org/apache/plc4x/java/opm/PlcField.java | 2 +-
.../apache/plc4x/java/opm/ConnectedEntityTest.java | 9 +-
.../org/apache/plc4x/java/opm/OpmUtilsTest.java | 10 +-
.../plc4x/java/opm/PlcEntityInterceptorTest.java | 22 +-
.../java/opm/PlcEntityManagerComplexTest.java | 41 +--
.../plc4x/java/opm/PlcEntityManagerTest.java | 316 ++++++++++++---------
.../plc4x/java/opm/SimpleAliasRegistryTest.java | 11 +-
14 files changed, 693 insertions(+), 300 deletions(-)
diff --git a/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockDevice.java b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockDevice.java
index b0e1075..999a4ac 100644
--- a/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockDevice.java
+++ b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockDevice.java
@@ -20,10 +20,15 @@
package org.apache.plc4x.java.mock;
import org.apache.commons.lang3.tuple.Pair;
-import org.apache.plc4x.java.api.model.PlcField;
+import org.apache.plc4x.java.api.messages.PlcSubscriptionEvent;
+import org.apache.plc4x.java.api.model.PlcConsumerRegistration;
+import org.apache.plc4x.java.api.model.PlcSubscriptionHandle;
import org.apache.plc4x.java.api.types.PlcResponseCode;
import org.apache.plc4x.java.base.messages.items.BaseDefaultFieldItem;
+import java.util.Collection;
+import java.util.function.Consumer;
+
/**
* Mock Object to do assertions on.
*/
@@ -31,7 +36,15 @@ public interface MockDevice {
Pair<PlcResponseCode, BaseDefaultFieldItem> read(String fieldQuery);
- // TODO Implement this
- // void write(String fieldQuery, BaseDefaultFieldItem value);
+ PlcResponseCode write(String fieldQuery, Object value);
+
+ Pair<PlcResponseCode, PlcSubscriptionHandle> subscribe(String fieldQuery);
+
+ void unsubscribe();
+
+ // TODO: this might not be right here as you are not really register at the device, rather on the connection
+ PlcConsumerRegistration register(Consumer<PlcSubscriptionEvent> consumer, Collection<PlcSubscriptionHandle> handles);
+ // TODO: this might not be right here as you are not really register at the device, rather on the connection
+ void unregister(PlcConsumerRegistration registration);
}
diff --git a/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockField.java b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockField.java
index e570b2b..b7dccc9 100644
--- a/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockField.java
+++ b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockField.java
@@ -21,16 +21,29 @@ package org.apache.plc4x.java.mock;
import org.apache.plc4x.java.api.model.PlcField;
+import java.util.List;
+
public class MockField implements PlcField {
private final String fieldQuery;
+ private final List<Object> values;
+
public MockField(String fieldQuery) {
this.fieldQuery = fieldQuery;
+ values = null;
+ }
+
+ public MockField(String fieldQuery, List<Object> values) {
+ this.fieldQuery = fieldQuery;
+ this.values = values;
}
public String getFieldQuery() {
return fieldQuery;
}
+ public List<Object> getValues() {
+ return values;
+ }
}
diff --git a/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockFieldHandler.java b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockFieldHandler.java
index a8893e2..055769f 100644
--- a/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockFieldHandler.java
+++ b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockFieldHandler.java
@@ -22,6 +22,7 @@ package org.apache.plc4x.java.mock;
import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
import org.apache.plc4x.java.api.model.PlcField;
import org.apache.plc4x.java.base.connection.DefaultPlcFieldHandler;
+import org.apache.plc4x.java.base.messages.items.BaseDefaultFieldItem;
public class MockFieldHandler extends DefaultPlcFieldHandler {
@@ -30,4 +31,73 @@ public class MockFieldHandler extends DefaultPlcFieldHandler {
return new MockField(fieldQuery);
}
+ @Override
+ public BaseDefaultFieldItem encodeBoolean(PlcField field, Object[] values) {
+ return new MockFieldItem(values);
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeByte(PlcField field, Object[] values) {
+ return new MockFieldItem(values);
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeShort(PlcField field, Object[] values) {
+ return new MockFieldItem(values);
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeInteger(PlcField field, Object[] values) {
+ return new MockFieldItem(values);
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeBigInteger(PlcField field, Object[] values) {
+ return new MockFieldItem(values);
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeLong(PlcField field, Object[] values) {
+ return new MockFieldItem(values);
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeFloat(PlcField field, Object[] values) {
+ return new MockFieldItem(values);
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeBigDecimal(PlcField field, Object[] values) {
+ return new MockFieldItem(values);
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeDouble(PlcField field, Object[] values) {
+ return new MockFieldItem(values);
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeString(PlcField field, Object[] values) {
+ return new MockFieldItem(values);
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeTime(PlcField field, Object[] values) {
+ return new MockFieldItem(values);
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeDate(PlcField field, Object[] values) {
+ return new MockFieldItem(values);
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeDateTime(PlcField field, Object[] values) {
+ return new MockFieldItem(values);
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeByteArray(PlcField field, Object[] values) {
+ return new MockFieldItem(values);
+ }
}
diff --git a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcField.java b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockFieldItem.java
similarity index 65%
copy from plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcField.java
copy to plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockFieldItem.java
index f6b499d..2942aa5 100644
--- a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcField.java
+++ b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/MockFieldItem.java
@@ -17,19 +17,18 @@
under the License.
*/
-package org.apache.plc4x.java.opm;
+package org.apache.plc4x.java.mock;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+import org.apache.plc4x.java.base.messages.items.BaseDefaultFieldItem;
+
+public class MockFieldItem extends BaseDefaultFieldItem<Object> {
+
+ public MockFieldItem(Object... values) {
+ super(values);
+ }
+
+ public Object getObject(int index) {
+ return getValue(index);
+ }
-/**
- * Field that is mapped
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.FIELD})
-public @interface PlcField {
- String value();
- long cacheDurationMillis() default 1000;
}
diff --git a/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/PlcMockConnection.java b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/PlcMockConnection.java
index 1972172..9dc802f 100644
--- a/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/PlcMockConnection.java
+++ b/plc4j/protocols/test/src/main/java/org/apache/plc4x/java/mock/PlcMockConnection.java
@@ -22,24 +22,24 @@ import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.plc4x.java.api.PlcConnection;
import org.apache.plc4x.java.api.authentication.PlcAuthentication;
-import org.apache.plc4x.java.api.exceptions.PlcUnsupportedOperationException;
import org.apache.plc4x.java.api.messages.*;
import org.apache.plc4x.java.api.metadata.PlcConnectionMetadata;
+import org.apache.plc4x.java.api.model.PlcConsumerRegistration;
+import org.apache.plc4x.java.api.model.PlcSubscriptionHandle;
import org.apache.plc4x.java.api.types.PlcResponseCode;
-import org.apache.plc4x.java.base.messages.DefaultPlcReadRequest;
-import org.apache.plc4x.java.base.messages.DefaultPlcReadResponse;
-import org.apache.plc4x.java.base.messages.PlcReader;
+import org.apache.plc4x.java.base.messages.*;
import org.apache.plc4x.java.base.messages.items.BaseDefaultFieldItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.Collection;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
import java.util.function.Function;
-import java.util.function.Supplier;
import java.util.stream.Collectors;
-public class PlcMockConnection implements PlcConnection, PlcReader {
+public class PlcMockConnection implements PlcConnection, PlcReader, PlcWriter, PlcSubscriber {
private static final Logger LOGGER = LoggerFactory.getLogger(PlcMockConnection.class);
@@ -85,12 +85,12 @@ public class PlcMockConnection implements PlcConnection, PlcReader {
@Override
public boolean canWrite() {
- return false;
+ return true;
}
@Override
public boolean canSubscribe() {
- return false;
+ return true;
}
};
}
@@ -102,36 +102,82 @@ public class PlcMockConnection implements PlcConnection, PlcReader {
@Override
public CompletableFuture<PlcReadResponse> read(PlcReadRequest readRequest) {
- return CompletableFuture.supplyAsync(new Supplier<PlcReadResponse>() {
+ return CompletableFuture.supplyAsync(() -> {
+ Validate.notNull(device, "No device is set in the mock connection!");
+ LOGGER.debug("Sending read request to MockDevice");
+ Map<String, Pair<PlcResponseCode, BaseDefaultFieldItem>> response = readRequest.getFieldNames().stream()
+ .collect(Collectors.toMap(
+ Function.identity(),
+ name -> device.read(((MockField) readRequest.getField(name)).getFieldQuery())
+ )
+ );
+ return new DefaultPlcReadResponse((DefaultPlcReadRequest) readRequest, response);
+ });
+ }
- @Override
- public PlcReadResponse get() {
- Validate.notNull(device, "No device is set in the mock connection!");
- LOGGER.debug("Sending read request to MockDevice");
- Map<String, Pair<PlcResponseCode, BaseDefaultFieldItem>> response = readRequest.getFieldNames().stream()
- .collect(Collectors.toMap(
- Function.identity(),
- name -> device.read(((MockField) readRequest.getField(name)).getFieldQuery())
- )
- );
- return new DefaultPlcReadResponse((DefaultPlcReadRequest)readRequest, response);
- }
+ @Override
+ public CompletableFuture<PlcWriteResponse> write(PlcWriteRequest writeRequest) {
+ return CompletableFuture.supplyAsync(() -> {
+ Validate.notNull(device, "No device is set in the mock connection!");
+ LOGGER.debug("Sending write request to MockDevice");
+ Map<String, PlcResponseCode> response = writeRequest.getFieldNames().stream()
+ .collect(Collectors.toMap(
+ Function.identity(),
+ name -> device.write(((MockField) writeRequest.getField(name)).getFieldQuery(), ((MockField) writeRequest.getField(name)).getValues())
+ )
+ );
+ return new DefaultPlcWriteResponse((DefaultPlcWriteRequest) writeRequest, response);
+ });
+ }
+
+ @Override
+ public CompletableFuture<PlcSubscriptionResponse> subscribe(PlcSubscriptionRequest subscriptionRequest) {
+ return CompletableFuture.supplyAsync(() -> {
+ Validate.notNull(device, "No device is set in the mock connection!");
+ LOGGER.debug("Sending subsribe request to MockDevice");
+ Map<String, Pair<PlcResponseCode, PlcSubscriptionHandle>> response = subscriptionRequest.getFieldNames().stream()
+ .collect(Collectors.toMap(
+ Function.identity(),
+ name -> device.subscribe(((MockField) subscriptionRequest.getField(name)).getFieldQuery())
+ )
+ );
+ return new DefaultPlcSubscriptionResponse((DefaultPlcSubscriptionRequest) subscriptionRequest, response);
});
}
@Override
+ public CompletableFuture<PlcUnsubscriptionResponse> unsubscribe(PlcUnsubscriptionRequest unsubscriptionRequest) {
+ return CompletableFuture.supplyAsync(() -> {
+ Validate.notNull(device, "No device is set in the mock connection!");
+ LOGGER.debug("Sending subsribe request to MockDevice");
+ device.unsubscribe();
+ return new DefaultPlcUnsubscriptionResponse((DefaultPlcUnsubscriptionRequest) unsubscriptionRequest);
+ });
+ }
+
+ @Override
+ public PlcConsumerRegistration register(Consumer<PlcSubscriptionEvent> consumer, Collection<PlcSubscriptionHandle> handles) {
+ return device.register(consumer, handles);
+ }
+
+ @Override
+ public void unregister(PlcConsumerRegistration registration) {
+ device.unregister(registration);
+ }
+
+ @Override
public PlcWriteRequest.Builder writeRequestBuilder() {
- throw new PlcUnsupportedOperationException("Write not supported by Mock Driver");
+ return new DefaultPlcWriteRequest.Builder(this, new MockFieldHandler());
}
@Override
public PlcSubscriptionRequest.Builder subscriptionRequestBuilder() {
- throw new PlcUnsupportedOperationException("Subscription not supported by Mock Driver");
+ return new DefaultPlcSubscriptionRequest.Builder(this, new MockFieldHandler());
}
@Override
public PlcUnsubscriptionRequest.Builder unsubscriptionRequestBuilder() {
- throw new PlcUnsupportedOperationException("Subscription not supported by Mock Driver");
+ return new DefaultPlcUnsubscriptionRequest.Builder(this);
}
public PlcAuthentication getAuthentication() {
diff --git a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java
index 7aee487..1a8953b 100644
--- a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java
+++ b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java
@@ -29,8 +29,7 @@ import org.apache.plc4x.java.PlcDriverManager;
import org.apache.plc4x.java.api.PlcConnection;
import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
-import org.apache.plc4x.java.api.messages.PlcReadRequest;
-import org.apache.plc4x.java.api.messages.PlcReadResponse;
+import org.apache.plc4x.java.api.messages.*;
import org.apache.plc4x.java.api.types.PlcResponseCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -53,9 +52,9 @@ import java.util.concurrent.TimeoutException;
/**
* Interceptor for dynamic functionality of @{@link PlcEntity}.
- * Basically, its {@link #intercept(Object, Method, Callable, String, PlcDriverManager, AliasRegistry, Map)} method is called for each
+ * Basically, its {@link #interceptGetter(Object, Method, Callable, String, PlcDriverManager, AliasRegistry, Map, Map)} method is called for each
* invocation of a method on a connected @{@link PlcEntity} and does then the dynamic part.
- *
+ * <p>
* For those not too familiar with the JVM's dispatch on can roughly imagine the intercept method being a "regular"
* method on the "proxied" entity and all parameters of the intercept method could then be access to local fields.
*
@@ -75,26 +74,27 @@ public class PlcEntityInterceptor {
/**
* Basic Intersector for all methods on the proxy object.
* It checks if the invoked method is a getter and if so, only retrieves the requested field, forwarding to
- * the {@link #fetchValueForGetter(Object, Method, PlcDriverManager, String, AliasRegistry, Map)} method.
+ * the {@link #fetchAndSetValueForGetter(Object, Method, PlcDriverManager, String, AliasRegistry, Map)} method.
* <p>
* If the field is no getter, then all fields are refreshed by calling {@link #refetchAllFields(Object, PlcDriverManager, String, AliasRegistry, Map)}
* and then, the method is invoked.
*
- * @param proxy Object to intercept
- * @param method Method that was intercepted
- * @param callable Callable to call the method after fetching the values
- * @param address Address of the plc (injected from private field)
+ * @param proxy Object to intercept
+ * @param method Method that was intercepted
+ * @param callable Callable to call the method after fetching the values
+ * @param address Address of the plc (injected from private field)
* @param driverManager DriverManager instance to use (injected from private field)
* @return possible result of the original methods invocation
* @throws OPMException Problems with plc / proxying
*/
@SuppressWarnings("unused")
@RuntimeType
- public static Object intercept(@This Object proxy, @Origin Method method, @SuperCall Callable<?> callable,
- @FieldValue(PlcEntityManager.PLC_ADDRESS_FIELD_NAME) String address,
- @FieldValue(PlcEntityManager.DRIVER_MANAGER_FIELD_NAME) PlcDriverManager driverManager,
- @FieldValue(PlcEntityManager.ALIAS_REGISTRY) AliasRegistry registry,
- @FieldValue(PlcEntityManager.LAST_FETCHED) Map<String, Instant> lastFetched) throws OPMException {
+ public static Object interceptGetter(@This Object proxy, @Origin Method method, @SuperCall Callable<?> callable,
+ @FieldValue(PlcEntityManager.PLC_ADDRESS_FIELD_NAME) String address,
+ @FieldValue(PlcEntityManager.DRIVER_MANAGER_FIELD_NAME) PlcDriverManager driverManager,
+ @FieldValue(PlcEntityManager.ALIAS_REGISTRY) AliasRegistry registry,
+ @FieldValue(PlcEntityManager.LAST_FETCHED) Map<String, Instant> lastFetched,
+ @FieldValue(PlcEntityManager.LAST_WRITTEN) Map<String, Instant> lastWritten) throws OPMException {
LOGGER.trace("Invoked method {} on connected PlcEntity {}", method.getName(), method.getDeclaringClass().getName());
// If "detached" (i.e. _driverManager is null) simply forward the call
@@ -115,7 +115,12 @@ public class PlcEntityInterceptor {
LOGGER.trace("Invoked method {} is getter, trying to find annotated field and return requested value",
method.getName());
- return fetchValueForGetter(proxy, method, driverManager, address, registry, lastFetched);
+ fetchAndSetValueForGetter(proxy, method, driverManager, address, registry, lastFetched);
+ try {
+ return callable.call();
+ } catch (Exception e) {
+ throw new OPMException("Unable to forward invocation " + method.getName() + " on connected PlcEntity", e);
+ }
}
if (method.getName().startsWith("is") && (method.getReturnType() == boolean.class || method.getReturnType() == Boolean.class)) {
@@ -125,7 +130,43 @@ public class PlcEntityInterceptor {
// Fetch single value
LOGGER.trace("Invoked method {} is boolean flag method, trying to find annotated field and return requested value",
method.getName());
- return fetchValueForIsGetter(proxy, method, driverManager, address, registry, lastFetched);
+ fetchAndSetValueForIsGetter(proxy, method, driverManager, address, registry, lastFetched);
+ try {
+ return callable.call();
+ } catch (Exception e) {
+ throw new OPMException("Unable to forward invocation " + method.getName() + " on connected PlcEntity", e);
+ }
+ }
+
+ // Fetch all values, than invoke method
+ try {
+ LOGGER.trace("Invoked method is no getter, refetch all fields and invoke method {} then", method.getName());
+ refetchAllFields(proxy, driverManager, address, registry, lastFetched);
+ Object call = callable.call();
+ // We write back
+ writeAllFields(proxy, driverManager, address, registry, lastWritten);
+ return call;
+ } catch (Exception e) {
+ throw new OPMException("Unable to forward invocation " + method.getName() + " on connected PlcEntity", e);
+ }
+ }
+
+ @RuntimeType
+ public static Object interceptSetter(@This Object proxy, @Origin Method method, @SuperCall Callable<?> callable,
+ @FieldValue(PlcEntityManager.PLC_ADDRESS_FIELD_NAME) String address,
+ @FieldValue(PlcEntityManager.DRIVER_MANAGER_FIELD_NAME) PlcDriverManager driverManager,
+ @FieldValue(PlcEntityManager.ALIAS_REGISTRY) AliasRegistry registry,
+ @FieldValue(PlcEntityManager.LAST_FETCHED) Map<String, Instant> lastFetched,
+ @Argument(0) Object argument) throws OPMException {
+ if (method.getName().startsWith("set")) {
+ if (method.getParameterCount() != 1) {
+ throw new OPMException("Only setter with one arguments are supported");
+ }
+ // Set single value
+ LOGGER.trace("Invoked method {} is setter, trying to find annotated field and return requested value",
+ method.getName());
+
+ return setValueForSetter(proxy, method, callable, driverManager, address, registry, lastFetched, argument);
}
// Fetch all values, than invoke method
@@ -141,9 +182,9 @@ public class PlcEntityInterceptor {
/**
* Renews all values of all Fields that are annotated with {@link PlcEntity}.
*
- * @param proxy Object to refresh the fields on.
+ * @param proxy Object to refresh the fields on.
* @param driverManager Driver Manager to use
- * @param registry AliasRegistry to use
+ * @param registry AliasRegistry to use
* @param lastFetched
* @throws OPMException on various errors.
*/
@@ -170,7 +211,7 @@ public class PlcEntityInterceptor {
Arrays.stream(entityClass.getDeclaredFields())
.filter(field -> field.isAnnotationPresent(PlcField.class))
- .filter(field -> needsToBeFetched(lastFetched, field))
+ .filter(field -> needsToBeSynced(lastFetched, field))
.forEach(field ->
requestBuilder.addItem(
getFqn(field),
@@ -204,34 +245,92 @@ public class PlcEntityInterceptor {
}
}
+ static void writeAllFields(Object proxy, PlcDriverManager driverManager, String address, AliasRegistry registry, Map<String, Instant> lastWritten) throws OPMException {
+ // Don't log o here as this would cause a second request against a plc so don't touch it, or if you log be aware of that
+ Class<?> entityClass = proxy.getClass().getSuperclass();
+ LOGGER.trace("Writing all fields on proxy object of class {}", entityClass);
+ PlcEntity plcEntity = entityClass.getAnnotation(PlcEntity.class);
+ if (plcEntity == null) {
+ throw new OPMException("Non PlcEntity supplied");
+ }
+
+ // Check if all fields are valid
+ for (Field field : entityClass.getDeclaredFields()) {
+ if (field.isAnnotationPresent(PlcField.class)) {
+ OpmUtils.getOrResolveAddress(registry, field.getAnnotation(PlcField.class).value());
+ }
+ }
+ try (PlcConnection connection = driverManager.getConnection(address)) {
+ // Catch the exception, if no reader present (see below)
+ // Build the query
+ PlcWriteRequest.Builder requestBuilder = connection.writeRequestBuilder();
+
+ Arrays.stream(entityClass.getDeclaredFields())
+ .filter(field -> field.isAnnotationPresent(PlcField.class))
+ .filter(field -> needsToBeSynced(lastWritten, field))
+ .forEach(field ->
+ requestBuilder.addItem(
+ getFqn(field),
+ OpmUtils.getOrResolveAddress(registry, field.getAnnotation(PlcField.class).value()),
+ getFromField(field, proxy)
+ )
+ );
+
+ PlcWriteRequest request = requestBuilder.build();
+
+ LOGGER.trace("Request for write of {} was build and is {}", entityClass, request);
+
+ PlcWriteResponse response = getPlcWriteResponse(request);
+
+ // Fill all requested fields
+ for (String fieldName : response.getFieldNames()) {
+ // Fill into Cache
+ lastWritten.put(fieldName, Instant.now());
+ }
+ } catch (PlcConnectionException e) {
+ throw new OPMException("Problem during processing", e);
+ } catch (Exception e) {
+ throw new OPMException("Unexpected error during processing", e);
+ }
+ }
+
+ private static Object getFromField(Field field, Object object) {
+ try {
+ field.setAccessible(true);
+ return field.get(object);
+ } catch (IllegalAccessException e) {
+ throw new PlcRuntimeException(e);
+ }
+ }
+
private static String getFqn(Field field) {
return field.getDeclaringClass().getName() + "." + field.getName();
}
/**
- * Checks if a field needs to be refetched, i.e., the cached values are too old.
+ * Checks if a field needs to be refetched/rewritten, i.e., the cached values are too old.
*/
- private static boolean needsToBeFetched(Map<String, Instant> lastFetched, Field field) {
+ private static boolean needsToBeSynced(Map<String, Instant> lastSynced, Field field) {
Validate.notNull(field);
long cacheDurationMillis = field.getAnnotation(PlcField.class).cacheDurationMillis();
String fqn = getFqn(field);
- if (lastFetched.containsKey(fqn)) {
- Instant last = lastFetched.get(fqn);
+ if (lastSynced.containsKey(fqn)) {
+ Instant last = lastSynced.get(fqn);
return Instant.now().minus(cacheDurationMillis, ChronoUnit.MILLIS).isAfter(last);
}
return true;
}
- private static Object fetchValueForIsGetter(Object proxy, Method m, PlcDriverManager driverManager, String address, AliasRegistry registry, Map<String, Instant> lastFetched) throws OPMException {
- return fetchValueForGetter(proxy, m, 2, driverManager, address, registry, lastFetched);
+ private static void fetchAndSetValueForIsGetter(Object proxy, Method m, PlcDriverManager driverManager, String address, AliasRegistry registry, Map<String, Instant> lastFetched) throws OPMException {
+ fetchAndSetValueForGetter(proxy, m, 2, driverManager, address, registry, lastFetched);
}
- private static Object fetchValueForGetter(Object proxy, Method m, PlcDriverManager driverManager, String address, AliasRegistry registry, Map<String, Instant> lastFetched) throws OPMException {
- return fetchValueForGetter(proxy, m, 3, driverManager, address, registry, lastFetched);
+ private static void fetchAndSetValueForGetter(Object proxy, Method m, PlcDriverManager driverManager, String address, AliasRegistry registry, Map<String, Instant> lastFetched) throws OPMException {
+ fetchAndSetValueForGetter(proxy, m, 3, driverManager, address, registry, lastFetched);
}
- private static Object fetchValueForGetter(Object proxy, Method m, int prefixLength, PlcDriverManager driverManager,
- String address, AliasRegistry registry, Map<String, Instant> lastFetched) throws OPMException {
+ private static void fetchAndSetValueForGetter(Object proxy, Method m, int prefixLength, PlcDriverManager driverManager,
+ String address, AliasRegistry registry, Map<String, Instant> lastFetched) throws OPMException {
String s = m.getName().substring(prefixLength);
// First char to lower
String variable = s.substring(0, 1).toLowerCase().concat(s.substring(1));
@@ -249,14 +348,8 @@ public class PlcEntityInterceptor {
String fqn = getFqn(field);
// Check if cache is still active
- if (!needsToBeFetched(lastFetched, field)) {
- // Return the current value
- try {
- field.setAccessible(true);
- return field.get(proxy);
- } catch (IllegalAccessException e) {
- throw new OPMException("Unable to restore cached (previous) value for field '" + field.getName() + "'", e);
- }
+ if (!needsToBeSynced(lastFetched, field)) {
+ return;
}
try (PlcConnection connection = driverManager.getConnection(address)) {
// Catch the exception, if no reader present (see below)
@@ -270,7 +363,61 @@ public class PlcEntityInterceptor {
// Fill into Cache
lastFetched.put(field.getName(), Instant.now());
- return getTyped(m.getReturnType(), response, fqn);
+ Object value = getTyped(m.getReturnType(), response, fqn);
+ setForField(field, proxy, value);
+ } catch (ClassCastException e) {
+ throw new OPMException("Unable to return response as suitable type", e);
+ } catch (Exception e) {
+ throw new OPMException("Problem during processing", e);
+ }
+ }
+
+ private static void setForField(Field field, Object proxy, Object value) {
+ try {
+ field.setAccessible(true);
+ field.set(proxy, value);
+ } catch (IllegalAccessException e) {
+ throw new PlcRuntimeException(e);
+ }
+ }
+
+ private static Object setValueForSetter(Object proxy, Method m, Callable<?> callable, PlcDriverManager driverManager,
+ String address, AliasRegistry registry, Map<String, Instant> lastFetched, Object object) throws OPMException {
+ String s = m.getName().substring(3);
+ // First char to lower
+ String variable = s.substring(0, 1).toLowerCase().concat(s.substring(1));
+ LOGGER.trace("Looking for field with name {} after invokation of getter {}", variable, m.getName());
+ PlcField annotation;
+ Field field;
+ try {
+ field = m.getDeclaringClass().getDeclaredField(variable);
+ annotation = field.getDeclaredAnnotation(PlcField.class);
+ } catch (NoSuchFieldException e) {
+ throw new OPMException("Unable to identify field with name '" + variable + "' for call to '" + m.getName() + "'", e);
+ }
+
+ // Use Fully qualified Name as field index
+ String fqn = getFqn(field);
+
+ try (PlcConnection connection = driverManager.getConnection(address)) {
+ // Catch the exception, if no reader present (see below)
+
+ PlcWriteRequest request = connection.writeRequestBuilder()
+ .addItem(fqn, OpmUtils.getOrResolveAddress(registry, annotation.value()), object)
+ .build();
+
+ PlcWriteResponse response = getPlcWriteResponse(request);
+
+ // Fill into Cache
+ lastFetched.put(field.getName(), Instant.now());
+
+ LOGGER.debug("getTyped clazz: {}, response: {}, fieldName: {}", m.getParameters()[0].getType(), response, fqn);
+ if (response.getResponseCode(fqn) != PlcResponseCode.OK) {
+ throw new PlcRuntimeException(String.format("Unable to read specified field '%s', response code was '%s'",
+ fqn, response.getResponseCode(fqn)));
+ }
+ callable.call();
+ return null;
} catch (ClassCastException e) {
throw new OPMException("Unable to return response as suitable type", e);
} catch (Exception e) {
@@ -278,6 +425,7 @@ public class PlcEntityInterceptor {
}
}
+
/**
* Tries to set a response Item to a field in the given object.
* This is one by looking for a field in the class and a response item
@@ -287,7 +435,7 @@ public class PlcEntityInterceptor {
* @param response Response to fetch the response from
* @param targetFieldName Name of the field in the object
* @param sourceFieldName Name of the field in the response
- * @throws NoSuchFieldException If a field is not present in entity
+ * @throws NoSuchFieldException If a field is not present in entity
* @throws IllegalAccessException If a field in the entity cannot be accessed
*/
static void setField(Class<?> clazz, Object o, PlcReadResponse response, String targetFieldName, String sourceFieldName) throws NoSuchFieldException, IllegalAccessException {
@@ -302,7 +450,8 @@ public class PlcEntityInterceptor {
}
}
- @SuppressWarnings({"squid:S3776", "squid:MethodCyclomaticComplexity"}) // Cognitive Complexity not too high, as highly structured
+ @SuppressWarnings({"squid:S3776", "squid:MethodCyclomaticComplexity"})
+ // Cognitive Complexity not too high, as highly structured
static Object getTyped(Class<?> clazz, PlcReadResponse response, String sourceFieldName) {
LOGGER.debug("getTyped clazz: {}, response: {}, fieldName: {}", clazz, response, sourceFieldName);
if (response.getResponseCode(sourceFieldName) != PlcResponseCode.OK) {
@@ -373,8 +522,24 @@ public class PlcEntityInterceptor {
* @throws OPMException on {@link InterruptedException} or {@link ExecutionException} or {@link TimeoutException}
*/
static PlcReadResponse getPlcReadResponse(PlcReadRequest request) throws OPMException {
+ return getFromFuture(request);
+ }
+
+ /**
+ * Fetch the request and do appropriate error handling
+ *
+ * @param request the request to get the exception from
+ * @return the response from the exception.
+ * @throws OPMException on {@link InterruptedException} or {@link ExecutionException} or {@link TimeoutException}
+ */
+ public static PlcWriteResponse getPlcWriteResponse(PlcWriteRequest request) throws OPMException {
+ return getFromFuture(request);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <REQ extends PlcRequest, RES extends PlcResponse> RES getFromFuture(REQ request) throws OPMException {
try {
- return request.execute().get(READ_TIMEOUT, TimeUnit.MILLISECONDS);
+ return (RES) request.execute().get(READ_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new OPMException("Exception during execution", e);
diff --git a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityManager.java b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityManager.java
index 81ebbea..7188d4e 100644
--- a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityManager.java
+++ b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityManager.java
@@ -22,21 +22,16 @@ package org.apache.plc4x.java.opm;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.implementation.MethodDelegation;
-import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.plc4x.java.PlcDriverManager;
-import org.apache.plc4x.java.api.PlcConnection;
-import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
-import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
import org.apache.plc4x.java.api.messages.PlcReadRequest;
-import org.apache.plc4x.java.api.messages.PlcReadResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.Instant;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
@@ -56,17 +51,17 @@ import static net.bytebuddy.matcher.ElementMatchers.not;
* <li>Needs to be annotated with {@link PlcEntity} and has a valid value which is the connection string</li>
* </ul>
* <p>
- * Basically, the {@link PlcEntityManager} has to operation "modes" represented by the methods {@link #read(Class,String)} and
- * {@link #connect(Class,String)}.
+ * Basically, the {@link PlcEntityManager} has to operation "modes" represented by the methods {@link #read(Class, String)} and
+ * {@link #connect(Class, String)}.
* <p>
* For a field to get Values from the Plc Injected it needs to be annotated with the {@link PlcField} annotation.
* The value has to be the plc fields string (which is inserted in the {@link PlcReadRequest}).
* The connection string is taken from the value of the {@link PlcEntity} annotation on the class.
* <p>
- * The {@link #read(Class,String)} method has no direkt equivalent in JPA (as far as I know) as it only returns a "detached"
+ * The {@link #read(Class, String)} method has no direkt equivalent in JPA (as far as I know) as it only returns a "detached"
* entity. This means it fetches all values from the plc that are annotated wiht the {@link PlcField} annotations.
* <p>
- * The {@link #connect(Class,String)} method is more JPA-like as it returns a "connected" entity. This means, that each
+ * The {@link #connect(Class, String)} method is more JPA-like as it returns a "connected" entity. This means, that each
* time one of the getters on the returned entity is called a call is made to the plc (and the field value is changed
* for this specific field).
* Furthermore, if a method which is no getter is called, then all {@link PlcField}s are refreshed before doing the call.
@@ -77,7 +72,7 @@ import static net.bytebuddy.matcher.ElementMatchers.not;
* regular Pojo it was before.
* <p>
* All invocations on the getters are forwarded to the
- * {@link PlcEntityInterceptor#intercept(Object, Method, Callable, String, PlcDriverManager, AliasRegistry, Map)}
+ * {@link PlcEntityInterceptor#interceptGetter(Object, Method, Callable, String, PlcDriverManager, AliasRegistry, Map, Map)}
* method.
*/
public class PlcEntityManager {
@@ -88,6 +83,7 @@ public class PlcEntityManager {
static final String DRIVER_MANAGER_FIELD_NAME = "_driverManager";
static final String ALIAS_REGISTRY = "_aliasRegistry";
public static final String LAST_FETCHED = "_lastFetched";
+ public static final String LAST_WRITTEN = "_lastWritten";
private final PlcDriverManager driverManager;
private final SimpleAliasRegistry registry;
@@ -106,48 +102,15 @@ public class PlcEntityManager {
}
public <T> T read(Class<T> clazz, String address) throws OPMException {
- PlcEntity annotation = OpmUtils.getPlcEntityAndCheckPreconditions(clazz);
-
- try (PlcConnection connection = driverManager.getConnection(address)) {
- if (!connection.getMetadata().canRead()) {
- throw new OPMException("Unable to get Reader for connection with url '" + address + "'");
- }
-
- PlcReadRequest.Builder requestBuilder = connection.readRequestBuilder();
-
- Arrays.stream(clazz.getDeclaredFields())
- .filter(field -> field.isAnnotationPresent(PlcField.class))
- .forEach(field ->
- requestBuilder.addItem(
- field.getDeclaringClass().getName() + "." + field.getName(),
- OpmUtils.getOrResolveAddress(registry, field.getAnnotation(PlcField.class).value())
- )
- );
-
- // Build the request
- PlcReadRequest request = requestBuilder.build();
-
- // Perform the request
- PlcReadResponse response = PlcEntityInterceptor.getPlcReadResponse(request);
-
- // Construct the Object
- T instance = clazz.getConstructor().newInstance();
+ T connect = connect(clazz, address);
+ disconnect(connect);
+ return connect;
+ }
- // Fill all requested fields
- for (String fieldName : response.getFieldNames()) {
- String targetFieldName = StringUtils.substringAfterLast(fieldName, ".");
- PlcEntityInterceptor.setField(clazz, instance, response, targetFieldName, fieldName);
- }
- return instance;
- } catch (PlcInvalidFieldException e) {
- throw new OPMException("Unable to parse field '" + e.getFieldToBeParsed() + "'", e);
- } catch (PlcConnectionException e) {
- throw new OPMException("Unable to get connection with url '" + address + "'", e);
- } catch (InstantiationException | InvocationTargetException | NoSuchMethodException | NoSuchFieldException | IllegalAccessException e) {
- throw new OPMException("Unable to fetch PlcEntity " + clazz.getName(), e);
- } catch (Exception e) {
- throw new OPMException("Unexpected Exception: " + e.getMessage(), e);
- }
+ public <T> T write(Class<T> clazz, String address, T object) throws OPMException {
+ T merge = merge(clazz, address, object);
+ disconnect(merge);
+ return merge;
}
/**
@@ -159,6 +122,23 @@ public class PlcEntityManager {
* @throws OPMException when proxy can't be build.
*/
public <T> T connect(Class<T> clazz, String address) throws OPMException {
+ return connect(clazz, address, null);
+ }
+
+
+ /**
+ * Returns a connected proxy.
+ *
+ * @param clazz clazz to be connected.
+ * @param <T> type of param {@code clazz}.
+ * @return a connected entity.
+ * @throws OPMException when proxy can't be build.
+ */
+ public <T> T merge(Class<T> clazz, String address, T instance) throws OPMException {
+ return connect(clazz, address, instance);
+ }
+
+ private <T> T connect(Class<T> clazz, String address, T existingInstance) throws OPMException {
OpmUtils.getPlcEntityAndCheckPreconditions(clazz);
try {
// Use Byte Buddy to generate a subclassed proxy that delegates all PlcField Methods
@@ -169,6 +149,7 @@ public class PlcEntityManager {
.defineField(DRIVER_MANAGER_FIELD_NAME, PlcDriverManager.class, Visibility.PRIVATE)
.defineField(ALIAS_REGISTRY, AliasRegistry.class, Visibility.PRIVATE)
.defineField(LAST_FETCHED, Map.class, Visibility.PRIVATE)
+ .defineField(LAST_WRITTEN, Map.class, Visibility.PRIVATE)
.method(not(isDeclaredBy(Object.class))).intercept(MethodDelegation.to(PlcEntityInterceptor.class))
.make()
.load(Thread.currentThread().getContextClassLoader())
@@ -181,9 +162,19 @@ public class PlcEntityManager {
FieldUtils.writeDeclaredField(instance, ALIAS_REGISTRY, registry, true);
Map<String, Instant> lastFetched = new HashMap<>();
FieldUtils.writeDeclaredField(instance, LAST_FETCHED, lastFetched, true);
+ Map<String, Instant> lastWritten = new HashMap<>();
+ FieldUtils.writeDeclaredField(instance, LAST_WRITTEN, lastWritten, true);
// Initially fetch all values
- PlcEntityInterceptor.refetchAllFields(instance, driverManager, address, registry, lastFetched);
+ if (existingInstance == null) {
+ PlcEntityInterceptor.refetchAllFields(instance, driverManager, address, registry, lastFetched);
+ } else {
+ FieldUtils.getAllFieldsList(clazz).stream()
+ .peek(field -> field.setAccessible(true))
+ .forEach(field -> setValueToField(field, instance, getValueFromField(field, existingInstance)));
+
+ PlcEntityInterceptor.writeAllFields(instance, driverManager, address, registry, lastWritten);
+ }
return instance;
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException | IllegalAccessError e) {
@@ -191,8 +182,25 @@ public class PlcEntityManager {
}
}
+ private Object getValueFromField(Field field, Object object) {
+ try {
+ return field.get(object);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void setValueToField(Field field, Object object, Object value) {
+ try {
+ field.set(object, value);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
/**
* Disconnects the given instance.
+ *
* @param entity Instance of a PlcEntity.
* @throws OPMException Is thrown when the plc is already disconnected or no entity.
*/
diff --git a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcField.java b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcField.java
index f6b499d..e6f16a4 100644
--- a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcField.java
+++ b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcField.java
@@ -28,7 +28,7 @@ import java.lang.annotation.Target;
* Field that is mapped
*/
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.FIELD})
+@Target({ElementType.FIELD})
public @interface PlcField {
String value();
long cacheDurationMillis() default 1000;
diff --git a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/ConnectedEntityTest.java b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/ConnectedEntityTest.java
index 0be09df..bf54364 100644
--- a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/ConnectedEntityTest.java
+++ b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/ConnectedEntityTest.java
@@ -26,7 +26,7 @@ import org.apache.plc4x.java.api.types.PlcResponseCode;
import org.apache.plc4x.java.base.messages.items.DefaultStringFieldItem;
import org.apache.plc4x.java.mock.MockDevice;
import org.apache.plc4x.java.mock.PlcMockConnection;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.util.stream.IntStream;
@@ -84,16 +84,17 @@ public class ConnectedEntityTest {
PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:cached");
MockDevice mock = Mockito.mock(MockDevice.class);
when(mock.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("hallo")));
+ when(mock.write(any(), any())).thenReturn(PlcResponseCode.OK);
connection.setDevice(mock);
PlcEntityManager entityManager = new PlcEntityManager(driverManager);
// Trigger a fetch
CachingEntity entity = entityManager.connect(CachingEntity.class, "mock:cached");
// Trigger Many Fetches via getter
- IntStream.range(1,100).forEach(i -> entity.getField());
- IntStream.range(1,100).forEach(i -> entity.dummyMethod());
+ IntStream.range(1, 100).forEach(i -> entity.getField());
+ IntStream.range(1, 100).forEach(i -> entity.dummyMethod());
- verify(mock, timeout(1_000).times(1)).read(any());
+ verify(mock, timeout(1_000).times(2)).read(any());
}
@PlcEntity
diff --git a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/OpmUtilsTest.java b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/OpmUtilsTest.java
index 27694d4..1753f0b 100644
--- a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/OpmUtilsTest.java
+++ b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/OpmUtilsTest.java
@@ -19,11 +19,12 @@
package org.apache.plc4x.java.opm;
-import org.junit.Test;
+import org.assertj.core.api.WithAssertions;
+import org.junit.jupiter.api.Test;
import static org.junit.Assert.*;
-public class OpmUtilsTest {
+public class OpmUtilsTest implements WithAssertions {
@Test
public void expression_matches() {
@@ -57,8 +58,9 @@ public class OpmUtilsTest {
assertFalse(OpmUtils.isValidExpression("${hallo"));
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void getAlias_illegalString_throws() {
- OpmUtils.getAlias("hallo");
+ assertThatThrownBy(() -> OpmUtils.getAlias("hallo"))
+ .isInstanceOf(IllegalArgumentException.class);
}
}
\ No newline at end of file
diff --git a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityInterceptorTest.java b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityInterceptorTest.java
index 301272b..d30fc92 100644
--- a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityInterceptorTest.java
+++ b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityInterceptorTest.java
@@ -24,7 +24,8 @@ import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
import org.apache.plc4x.java.api.messages.PlcReadRequest;
import org.apache.plc4x.java.api.types.PlcResponseCode;
import org.apache.plc4x.java.base.messages.DefaultPlcReadResponse;
-import org.junit.Test;
+import org.assertj.core.api.WithAssertions;
+import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
import org.slf4j.Logger;
@@ -42,7 +43,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.when;
-public class PlcEntityInterceptorTest {
+public class PlcEntityInterceptorTest implements WithAssertions {
private static final Logger LOGGER = LoggerFactory.getLogger(PlcEntityInterceptorTest.class);
@@ -78,20 +79,22 @@ public class PlcEntityInterceptorTest {
assertTrue(exceptionWasThrown.get());
}
- @Test(expected = OPMException.class)
- public void getPlcReadResponse_catchesExecutionException_rethrows() throws OPMException, InterruptedException, ExecutionException, TimeoutException {
- runGetPlcResponseWIthException(invocation -> {
+ @Test
+ public void getPlcReadResponse_catchesExecutionException_rethrows() {
+ assertThatThrownBy(() -> runGetPlcResponseWIthException(invocation -> {
throw new ExecutionException(new Exception());
- });
+ }))
+ .isInstanceOf(OPMException.class);
}
- @Test(expected = OPMException.class)
- public void getPlcReadResponse_timeoutOnGet_rethrows() throws OPMException {
+ @Test
+ public void getPlcReadResponse_timeoutOnGet_rethrows() {
PlcReadRequest request = Mockito.mock(PlcReadRequest.class);
CompletableFuture future = new CompletableFuture<>();
when(request.execute()).thenReturn(future);
- PlcEntityInterceptor.getPlcReadResponse(request);
+ assertThatThrownBy(() -> PlcEntityInterceptor.getPlcReadResponse(request))
+ .isInstanceOf(OPMException.class);
}
@Test
@@ -128,7 +131,6 @@ public class PlcEntityInterceptorTest {
}
// Getter with no field
- @PlcField("field1")
public String getField1() {
return "";
}
diff --git a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerComplexTest.java b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerComplexTest.java
index 4805516..1a310e1 100644
--- a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerComplexTest.java
+++ b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerComplexTest.java
@@ -25,17 +25,14 @@ import org.apache.plc4x.java.PlcDriverManager;
import org.apache.plc4x.java.api.PlcConnection;
import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
-import org.apache.plc4x.java.api.messages.PlcReadRequest;
import org.apache.plc4x.java.api.metadata.PlcConnectionMetadata;
import org.apache.plc4x.java.api.types.PlcResponseCode;
import org.apache.plc4x.java.base.connection.PlcFieldHandler;
-import org.apache.plc4x.java.base.messages.DefaultPlcReadRequest;
-import org.apache.plc4x.java.base.messages.DefaultPlcReadResponse;
-import org.apache.plc4x.java.base.messages.InternalPlcReadRequest;
-import org.apache.plc4x.java.base.messages.PlcReader;
+import org.apache.plc4x.java.base.messages.*;
import org.apache.plc4x.java.base.messages.items.*;
+import org.assertj.core.api.WithAssertions;
import org.junit.Assert;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
@@ -56,22 +53,24 @@ import static org.junit.Assert.*;
import static org.mockito.Mockito.when;
-public class PlcEntityManagerComplexTest {
+public class PlcEntityManagerComplexTest implements WithAssertions {
private PlcDriverManager driverManager;
- @Test(expected = IllegalArgumentException.class)
- public void noEntity_throws() throws OPMException {
+ @Test
+ public void noEntity_throws() {
PlcEntityManager manager = new PlcEntityManager();
- manager.read(NoEntity.class, "s7://localhost:5555/0/0");
+ assertThatThrownBy(() -> manager.read(NoEntity.class, "s7://localhost:5555/0/0"))
+ .isInstanceOf(IllegalArgumentException.class);
}
- @Test(expected = IllegalArgumentException.class)
- public void noValidConstructor_throws() throws OPMException {
+ @Test
+ public void noValidConstructor_throws() {
PlcEntityManager manager = new PlcEntityManager();
- manager.read(EntityWithBadConstructor.class, "s7://localhost:5555/0/0");
+ assertThatThrownBy(() -> manager.read(EntityWithBadConstructor.class, "s7://localhost:5555/0/0"))
+ .isInstanceOf(IllegalArgumentException.class);
}
@Test
@@ -174,14 +173,15 @@ public class PlcEntityManagerComplexTest {
assertNotNull(connected.getByteVar());
}
- @Test(expected = OPMException.class)
+ @Test
public void disconnectTwice_throwsException() throws PlcConnectionException, OPMException {
- PlcEntityManager manager = getPlcEntityManager(new HashMap<>());
+ PlcEntityManager manager = getInitializedEntityManager();
ConnectedEntity connected = manager.connect(ConnectedEntity.class, "s7://localhost:5555/0/0");
manager.disconnect(connected);
- manager.disconnect(connected);
+ assertThatThrownBy(() -> manager.disconnect(connected))
+ .isInstanceOf(OPMException.class);
}
private PlcEntityManager getPlcEntityManager(final Map<String, BaseDefaultFieldItem> responses) throws PlcConnectionException {
@@ -216,6 +216,15 @@ public class PlcEntityManagerComplexTest {
return CompletableFuture.completedFuture(new DefaultPlcReadResponse((InternalPlcReadRequest) readRequest, map));
};
when(connection.readRequestBuilder()).then(invocation -> new DefaultPlcReadRequest.Builder(reader, getFieldHandler()));
+ PlcWriter writer = writeRequest -> {
+ Map<String, PlcResponseCode> map = writeRequest.getFieldNames().stream()
+ .collect(Collectors.toMap(
+ Function.identity(),
+ s -> PlcResponseCode.OK
+ ));
+ return CompletableFuture.completedFuture(new DefaultPlcWriteResponse((InternalPlcWriteRequest) writeRequest, map));
+ };
+ when(connection.writeRequestBuilder()).then(invocation -> new DefaultPlcWriteRequest.Builder(writer, getFieldHandler()));
return new PlcEntityManager(mock);
}
diff --git a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java
index e97bf6e..91a6f9e 100644
--- a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java
+++ b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/PlcEntityManagerTest.java
@@ -30,169 +30,212 @@ import org.apache.plc4x.java.api.types.PlcResponseCode;
import org.apache.plc4x.java.base.messages.items.DefaultStringFieldItem;
import org.apache.plc4x.java.mock.MockDevice;
import org.apache.plc4x.java.mock.PlcMockConnection;
-import org.junit.Test;
+import org.assertj.core.api.WithAssertions;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
-public class PlcEntityManagerTest {
-
- @Test
- public void read_throwsInvalidFieldException_rethrows() throws PlcConnectionException {
- // Prepare the Mock
- PlcDriverManager driverManager = Mockito.mock(PlcDriverManager.class);
- PlcConnection connection = Mockito.mock(PlcConnection.class);
- PlcConnectionMetadata metadata = Mockito.mock(PlcConnectionMetadata.class);
- PlcReadRequest.Builder builder = Mockito.mock(PlcReadRequest.Builder.class);
- when(metadata.canRead()).thenReturn(true);
- when(connection.readRequestBuilder()).thenReturn(builder);
- when(connection.getMetadata()).thenReturn(metadata);
- when(builder.build()).thenThrow(new PlcInvalidFieldException("field1"));
- when(driverManager.getConnection(any())).thenReturn(connection);
-
- // Create Entity Manager
- PlcEntityManager entityManager = new PlcEntityManager(driverManager);
-
- // Issue Call to trigger interception
- String message = null;
- try {
- BadEntity entity = entityManager.read(BadEntity.class, "mock:test");
- } catch (Exception e) {
- message = e.getMessage();
- }
-
- assertEquals("Unable to parse field 'field1'", message);
- }
+public class PlcEntityManagerTest implements WithAssertions {
+
+ @Nested
+ class Read {
+ @Test
+ public void throwsInvalidFieldException_rethrows() throws PlcConnectionException {
+ // Prepare the Mock
+ PlcDriverManager driverManager = Mockito.mock(PlcDriverManager.class);
+ PlcConnection connection = Mockito.mock(PlcConnection.class);
+ PlcConnectionMetadata metadata = Mockito.mock(PlcConnectionMetadata.class);
+ PlcReadRequest.Builder builder = Mockito.mock(PlcReadRequest.Builder.class);
+ when(metadata.canRead()).thenReturn(true);
+ when(connection.readRequestBuilder()).thenReturn(builder);
+ when(connection.getMetadata()).thenReturn(metadata);
+ when(builder.build()).thenThrow(new PlcInvalidFieldException("field1"));
+ when(driverManager.getConnection(any())).thenReturn(connection);
+
+ // Create Entity Manager
+ PlcEntityManager entityManager = new PlcEntityManager(driverManager);
+
+ // Issue Call to trigger interception
+ assertThatThrownBy(() -> entityManager.read(BadEntity.class, "mock:test"))
+ .hasCauseInstanceOf(PlcInvalidFieldException.class)
+ .hasStackTraceContaining("field1 invalid");
+ }
- @Test
- public void read_unableToConnect_rethrows() throws PlcConnectionException {
- // Prepare the Mock
- PlcDriverManager driverManager = Mockito.mock(PlcDriverManager.class);
- when(driverManager.getConnection(any())).thenThrow(new PlcConnectionException(""));
+ @Test
+ public void unableToConnect_rethrows() throws PlcConnectionException {
+ // Prepare the Mock
+ PlcDriverManager driverManager = Mockito.mock(PlcDriverManager.class);
+ when(driverManager.getConnection(any())).thenThrow(new PlcConnectionException(""));
- // Create Entity Manager
- PlcEntityManager entityManager = new PlcEntityManager(driverManager);
+ // Create Entity Manager
+ PlcEntityManager entityManager = new PlcEntityManager(driverManager);
- // Issue Call to trigger interception
- String message = null;
- try {
- BadEntity entity = entityManager.read(BadEntity.class, "mock:test");
- } catch (Exception e) {
- message = e.getMessage();
+ // Issue Call to trigger interception
+ assertThatThrownBy(() -> entityManager.read(BadEntity.class, "mock:test"))
+ .hasCauseInstanceOf(PlcConnectionException.class)
+ .hasStackTraceContaining("Problem during processing");
}
- assertEquals("Unable to get connection with url 'mock:test'", message);
- }
-
- @Test(expected = OPMException.class)
- public void read_timeoutOnGet_throwsException() throws PlcConnectionException, OPMException {
- // Prepare the Mock
- MockDevice mockDevice = Mockito.mock(MockDevice.class);
- PlcDriverManager driverManager = new PlcDriverManager();
- PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:test");
- when(mockDevice.read(any())).thenAnswer(new Answer<Object>() {
- @Override
- public Object answer(InvocationOnMock invocation) throws Throwable {
+ @Test
+ public void timeoutOnGet_throwsException() throws PlcConnectionException {
+ // Prepare the Mock
+ MockDevice mockDevice = Mockito.mock(MockDevice.class);
+ PlcDriverManager driverManager = new PlcDriverManager();
+ PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:test");
+ when(mockDevice.read(any())).thenAnswer(invocation -> {
// Sleep for 3s
Thread.sleep(3_000);
return Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("Hallo"));
- }
- });
- connection.setDevice(mockDevice);
-
- // Create Entity Manager
- PlcEntityManager entityManager = new PlcEntityManager(driverManager);
+ });
+ connection.setDevice(mockDevice);
- // Issue Call which SHOULD timeout
- BadEntity entity = entityManager.read(BadEntity.class, "mock:test");
- }
+ // Create Entity Manager
+ PlcEntityManager entityManager = new PlcEntityManager(driverManager);
- @Test(expected = OPMException.class)
- public void read_uninstantiableEntity_throws() throws OPMException {
- PlcEntityManager entityManager = new PlcEntityManager();
+ // Issue Call which SHOULD timeout
+ assertThatThrownBy(() -> entityManager.read(BadEntity.class, "mock:test"))
+ .isInstanceOf(OPMException.class);
+ }
- UninstantiableEntity entity = entityManager.read(UninstantiableEntity.class, "mock:test");
- }
+ @Test
+ public void uninstantiableEntity_throws() {
+ PlcEntityManager entityManager = new PlcEntityManager();
- /**
- * Class is private, so EntityManager has no access to it
- * @throws OPMException
- */
- @Test(expected = OPMException.class)
- public void connect_uninstantiableEntity_throws() throws OPMException {
- PlcEntityManager entityManager = new PlcEntityManager();
+ assertThatThrownBy(() -> entityManager.read(UninstantiableEntity.class, "mock:test"))
+ .isInstanceOf(OPMException.class);
+ }
- UninstantiableEntity entity = entityManager.connect(UninstantiableEntity.class, "mock:test");
- }
+ @Test
+ public void resolveAlias_works() throws OPMException, PlcConnectionException {
+ SimpleAliasRegistry registry = new SimpleAliasRegistry();
+ registry.register("alias", "real_field");
- @Test
- public void read_resolveAlias_works() throws OPMException, PlcConnectionException {
- SimpleAliasRegistry registry = new SimpleAliasRegistry();
- registry.register("alias", "real_field");
+ // Mock
+ PlcDriverManager driverManager = new PlcDriverManager();
+ PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:test");
+ MockDevice mockDevice = Mockito.mock(MockDevice.class);
+ when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("value")));
+ connection.setDevice(mockDevice);
- // Mock
- PlcDriverManager driverManager = new PlcDriverManager();
- PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:test");
- MockDevice mockDevice = Mockito.mock(MockDevice.class);
- when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("value")));
- connection.setDevice(mockDevice);
+ PlcEntityManager entityManager = new PlcEntityManager(driverManager, registry);
+ entityManager.read(AliasEntity.class, "mock:test");
- PlcEntityManager entityManager = new PlcEntityManager(driverManager, registry);
- entityManager.read(AliasEntity.class, "mock:test");
+ // Assert that "field" was queried
+ verify(mockDevice).read(eq("real_field"));
+ }
- // Assert that "field" was queried
- verify(mockDevice).read(eq("real_field"));
- }
- @Test
- public void connect_resolveAlias_works() throws PlcConnectionException, OPMException {
- SimpleAliasRegistry registry = new SimpleAliasRegistry();
- registry.register("alias", "real_field");
+ @Test
+ public void unknownAlias_throws() {
+ PlcEntityManager entityManager = new PlcEntityManager();
- // Mock
- PlcDriverManager driverManager = new PlcDriverManager();
- PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:test");
- MockDevice mockDevice = Mockito.mock(MockDevice.class);
- when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("value")));
- connection.setDevice(mockDevice);
+ assertThatThrownBy(() -> entityManager.read(AliasEntity.class, "mock:test"))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
- PlcEntityManager entityManager = new PlcEntityManager(driverManager, registry);
- entityManager.connect(AliasEntity.class, "mock:test");
+ @Test
+ public void badAlias_throws() {
+ PlcEntityManager entityManager = new PlcEntityManager();
+
+ String message = null;
+ try {
+ entityManager.read(BadAliasEntity.class, "mock:test");
+ } catch (IllegalArgumentException e) {
+ message = e.getMessage();
+ } catch (OPMException e) {
+ fail("Unexpected Exception" + e);
+ }
- // Assert that "field" was queried
- verify(mockDevice, times(1)).read(eq("real_field"));
+ assertNotNull(message);
+ assertTrue(message.contains("Invalid Syntax, either use field address (no starting $) or an alias with Syntax ${xxx}. But given was"));
+ }
}
- @Test(expected = OPMException.class)
- public void read_unknownAlias_throws() throws OPMException {
- PlcEntityManager entityManager = new PlcEntityManager();
+ @Nested
+ class Write {
+ @Test
+ void simpleWrite() throws Exception {
+ SimpleAliasRegistry registry = new SimpleAliasRegistry();
+ registry.register("alias", "real_field");
+
+ // Mock
+ PlcDriverManager driverManager = new PlcDriverManager();
+ PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:test");
+ MockDevice mockDevice = Mockito.mock(MockDevice.class);
+ when(mockDevice.write(anyString(), any())).thenReturn(PlcResponseCode.OK);
+ when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("value")));
+ connection.setDevice(mockDevice);
+
+ PlcEntityManager entityManager = new PlcEntityManager(driverManager, registry);
+ AliasEntity object = new AliasEntity();
+ object.setAliasedField("changed");
+ AliasEntity connected = entityManager.write(AliasEntity.class, "mock:test", object);
+ connected.setAliasedField("changed2");
+ connected.getAliasedField();
+
+ // Assert that "field" was queried
+ verify(mockDevice, times(1)).read(eq("real_field"));
+ verify(mockDevice, times(2)).write(eq("real_field"), any());
+
+ entityManager.disconnect(connected);
+ assertThat(connected.getAliasedField()).isEqualTo("value");
+ }
- entityManager.read(AliasEntity.class, "mock:test");
+ @Test
+ void simpleWrite_uses_getter() throws Exception {
+ // Mock
+ PlcDriverManager driverManager = new PlcDriverManager();
+ PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:test");
+ MockDevice mockDevice = Mockito.mock(MockDevice.class);
+ when(mockDevice.write(anyString(), any())).thenReturn(PlcResponseCode.OK);
+ when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("value")));
+ connection.setDevice(mockDevice);
+
+ PlcEntityManager entityManager = new PlcEntityManager(driverManager);
+ CustomGetterEntity connect = entityManager.connect(CustomGetterEntity.class, "mock:test");
+ assertThat(connect.getAsd()).isEqualTo("value!");
+ }
}
- @Test
- public void read_badAlias_throws() {
- PlcEntityManager entityManager = new PlcEntityManager();
-
- String message = null;
- try {
- entityManager.read(BadAliasEntity.class, "mock:test");
- } catch (OPMException e) {
- message = e.getMessage();
+ @Nested
+ class Lifecycle {
+ /**
+ * Class is private, so EntityManager has no access to it
+ *
+ * @throws OPMException
+ */
+ @Test
+ public void connect_uninstantiableEntity_throws() {
+ PlcEntityManager entityManager = new PlcEntityManager();
+
+ assertThatThrownBy(() -> entityManager.connect(UninstantiableEntity.class, "mock:test"))
+ .isInstanceOf(OPMException.class);
}
- assertNotNull(message);
- assertTrue(message.contains("Invalid Syntax, either use field address (no starting $) or an alias with Syntax ${xxx}. But given was"));
+ @Test
+ public void connect_resolveAlias_works() throws PlcConnectionException, OPMException {
+ SimpleAliasRegistry registry = new SimpleAliasRegistry();
+ registry.register("alias", "real_field");
+
+ // Mock
+ PlcDriverManager driverManager = new PlcDriverManager();
+ PlcMockConnection connection = (PlcMockConnection) driverManager.getConnection("mock:test");
+ MockDevice mockDevice = Mockito.mock(MockDevice.class);
+ when(mockDevice.read(any())).thenReturn(Pair.of(PlcResponseCode.OK, new DefaultStringFieldItem("value")));
+ connection.setDevice(mockDevice);
+
+ PlcEntityManager entityManager = new PlcEntityManager(driverManager, registry);
+ entityManager.connect(AliasEntity.class, "mock:test");
+
+ // Assert that "field" was queried
+ verify(mockDevice, times(1)).read(eq("real_field"));
+ }
}
@PlcEntity
@@ -232,6 +275,10 @@ public class PlcEntityManagerTest {
public String getAliasedField() {
return aliasedField;
}
+
+ public void setAliasedField(String aliasedField) {
+ this.aliasedField = aliasedField;
+ }
}
@PlcEntity
@@ -249,4 +296,19 @@ public class PlcEntityManagerTest {
}
}
+ @PlcEntity
+ public static class CustomGetterEntity {
+
+ @PlcField("asd")
+ private String asd;
+
+ public CustomGetterEntity() {
+ // for OPM
+ }
+
+ public String getAsd() {
+ return asd + "!";
+ }
+ }
+
}
\ No newline at end of file
diff --git a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/SimpleAliasRegistryTest.java b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/SimpleAliasRegistryTest.java
index bc6d8ad..5e64d58 100644
--- a/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/SimpleAliasRegistryTest.java
+++ b/plc4j/utils/opm/src/test/java/org/apache/plc4x/java/opm/SimpleAliasRegistryTest.java
@@ -19,14 +19,15 @@
package org.apache.plc4x.java.opm;
-import org.junit.Test;
+import org.assertj.core.api.WithAssertions;
+import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.NoSuchElementException;
import static org.junit.Assert.*;
-public class SimpleAliasRegistryTest {
+public class SimpleAliasRegistryTest implements WithAssertions {
public static final String ADDRESS = "DB2:1234";
public static final String ALIAS = "some_field";
@@ -57,11 +58,13 @@ public class SimpleAliasRegistryTest {
assertFalse(registry.canResolve(ALIAS));
}
- @Test(expected = NoSuchElementException.class)
+ @Test
public void resolve_unknownAlias_throws() {
SimpleAliasRegistry registry = new SimpleAliasRegistry();
- registry.resolve(ALIAS);
+ assertThatThrownBy(() -> registry.resolve(ALIAS))
+ .isInstanceOf(NoSuchElementException.class);
+
}
private void checkMethods(SimpleAliasRegistry registry) {