You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by jf...@apache.org on 2018/10/27 11:48:17 UTC

[incubator-plc4x] branch master updated (2906972 -> a5ae69b)

This is an automated email from the ASF dual-hosted git repository.

jfeinauer pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git.


    from 2906972  fix build
     new 29c83a2  [OPM] Refactoring. Added PlcEntityInterceptor.
     new a5ae69b  [OPM] Added Documentation, some refactoring.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../java/org/apache/plc4x/java/opm/OpmUtils.java   |  12 -
 ...ntityManager.java => PlcEntityInterceptor.java} | 243 +++++------------
 .../apache/plc4x/java/opm/PlcEntityManager.java    | 296 +++------------------
 .../java/org/apache/plc4x/java/opm/PlcField.java   |   3 +
 .../utils/opm/src/site/asciidoc/opm/using-opm.adoc |  61 +++++
 plc4j/utils/opm/src/site/site.xml                  |  30 +++
 .../plc4x/java/opm/PlcEntityManagerTest.java       |  87 +++---
 7 files changed, 240 insertions(+), 492 deletions(-)
 copy plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/{PlcEntityManager.java => PlcEntityInterceptor.java} (55%)
 create mode 100644 plc4j/utils/opm/src/site/asciidoc/opm/using-opm.adoc
 create mode 100644 plc4j/utils/opm/src/site/site.xml


[incubator-plc4x] 01/02: [OPM] Refactoring. Added PlcEntityInterceptor.

Posted by jf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jfeinauer pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git

commit 29c83a29ff9ebdb124ea10e469d876dede412f26
Author: Julian Feinauer <j....@pragmaticminds.de>
AuthorDate: Sat Oct 27 11:56:11 2018 +0200

    [OPM] Refactoring. Added PlcEntityInterceptor.
---
 .../java/org/apache/plc4x/java/opm/OpmUtils.java   |  12 -
 ...ntityManager.java => PlcEntityInterceptor.java} | 229 ++++-------------
 .../apache/plc4x/java/opm/PlcEntityManager.java    | 282 +--------------------
 3 files changed, 67 insertions(+), 456 deletions(-)

diff --git a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/OpmUtils.java b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/OpmUtils.java
index 39f447d..604866d 100644
--- a/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/OpmUtils.java
+++ b/plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/OpmUtils.java
@@ -44,16 +44,4 @@ public final class OpmUtils {
         return annotation;
     }
 
-    static String extractAddress(Object proxy) throws OPMException {
-        String address;
-        try {
-            Field field = proxy.getClass().getDeclaredField(PlcEntityManager.PLC_ADDRESS_FIELD_NAME);
-            field.setAccessible(true);
-            address = (String) field.get(proxy);
-        } catch (IllegalAccessException | NoSuchFieldException e) {
-            throw new OPMException("Problem with accessing internal plc address", e);
-        }
-        return address;
-    }
-
 }
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/PlcEntityInterceptor.java
similarity index 56%
copy from plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityManager.java
copy to plc4j/utils/opm/src/main/java/org/apache/plc4x/java/opm/PlcEntityInterceptor.java
index d6d6de6..48df70e 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/PlcEntityInterceptor.java
@@ -1,36 +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.
+ * 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.opm;
 
-import net.bytebuddy.ByteBuddy;
-import net.bytebuddy.description.modifier.Visibility;
-import net.bytebuddy.implementation.MethodDelegation;
 import net.bytebuddy.implementation.bind.annotation.*;
 import org.apache.commons.configuration2.Configuration;
 import org.apache.commons.configuration2.SystemConfiguration;
 import org.apache.commons.lang3.ArrayUtils;
 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.exceptions.PlcRuntimeException;
 import org.apache.plc4x.java.api.messages.PlcReadRequest;
 import org.apache.plc4x.java.api.messages.PlcReadResponse;
@@ -38,7 +35,6 @@ 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.math.BigDecimal;
 import java.math.BigInteger;
@@ -51,164 +47,52 @@ import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
-import static net.bytebuddy.matcher.ElementMatchers.any;
-
 /**
- * Plc4x equivalent of Jpas EntityManager for implementing Object-Plc-Mapping.
- * This means that calls to a plc can be done by using plain POJOs with Annotations.
- * <p>
- * First, the necessary annotations are {@link PlcEntity} and {@link PlcField}.
- * For a class to be usable as PlcEntity it needs
- * <ul>
- * <li>be non-final (as proxiing has to be used in case of {@link #connect(Class, String)}</li>
- * <li>a public no args constructor for instanciation</li>
- * <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)}.
- * <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"
- * 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
- * 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.
- * Thus, all operations on fields that are annotated with {@link PlcField} are always done against the "live" values
- * from the PLC.
- * <p>
- * // TODO Add detach method
+ * Interceptor for dynamic functionality of @{@link PlcEntity}.
+ * Basically, its {@link #intercept(Object, Method, Callable, String, PlcDriverManager)} method is called for each
+ * invocation of a method on a connected @{@link PlcEntity} and does then the dynamic part.
+ *
+ * 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.
+ *
+ * @author julian
  */
-public class PlcEntityManager {
+public class PlcEntityInterceptor {
 
-    public static final String PLC_ADDRESS_FIELD_NAME = "_plcAddress";
-    private static final Logger LOGGER = LoggerFactory.getLogger(PlcEntityManager.class);
+    private static final Logger LOGGER = LoggerFactory.getLogger(PlcEntityInterceptor.class);
 
     private static final Configuration CONF = new SystemConfiguration();
     private static final long READ_TIMEOUT = CONF.getLong("org.apache.plc4x.java.opm.entity_manager.read_timeout", 1_000);
-
-    private final PlcDriverManager driverManager;
-
-    public PlcEntityManager() {
-        this.driverManager = new PlcDriverManager();
-    }
-
-    public PlcEntityManager(PlcDriverManager driverManager) {
-        this.driverManager = driverManager;
-    }
-
-    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(),
-                        field.getAnnotation(PlcField.class).value()
-                    )
-                );
-
-            // Build the request
-            PlcReadRequest request = requestBuilder.build();
-
-            // Perform the request
-            PlcReadResponse response = getPlcReadResponse(request);
-
-            // Construct the Object
-            T instance = clazz.getConstructor().newInstance();
-
-            // Fill all requested fields
-            for (String fieldName : response.getFieldNames()) {
-                String targetFieldName = StringUtils.substringAfterLast(fieldName, ".");
-                setField(clazz, instance, response, targetFieldName, fieldName);
-            }
-            return instance;
-        } catch (PlcInvalidFieldException e) {
-            throw new OPMException("Unable to parse one field request", 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("Unknown Error", e);
-        }
-    }
-
-    /**
-     * 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 connect(Class<T> clazz, String address) throws OPMException {
-        OpmUtils.getPlcEntityAndCheckPreconditions(clazz);
-        try {
-            // Use Byte Buddy to generate a subclassed proxy that delegates all PlcField Methods
-            // to the intercept method
-            T instance = new ByteBuddy()
-                .subclass(clazz)
-                .defineField(PLC_ADDRESS_FIELD_NAME, String.class, Visibility.PRIVATE)
-                .method(any()).intercept(MethodDelegation.to(this))
-                .make()
-                .load(Thread.currentThread().getContextClassLoader())
-                .getLoaded()
-                .getConstructor()
-                .newInstance();
-            // Set connection value into the private field
-            Field plcAddress = instance.getClass().getDeclaredField(PLC_ADDRESS_FIELD_NAME);
-            plcAddress.setAccessible(true);
-            plcAddress.set(instance, address);
-            return instance;
-        } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
-            throw new OPMException("Unable to instantiate Proxy", e);
-        } catch (NoSuchFieldException e) {
-            throw new IllegalStateException("Problem with field injection during proxy generation", e);
-        }
-    }
-
-    //------------------------------------------------------------------------------------------------
-    //
-    //  Methods for interception for the proxy object
-    //
-    //------------------------------------------------------------------------------------------------
-
     /**
      * 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(Method, String)} method.
+     * the {@link #fetchValueForGetter(Method, PlcDriverManager,String)} method.
      * <p>
-     * If the field is no getter, then all fields are refreshed by calling {@link #refetchAllFields(Object,String)}
+     * If the field is no getter, then all fields are refreshed by calling {@link #refetchAllFields(Object, PlcDriverManager, String)}
      * 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 entity   Reference to the PlcEntity
      * @return possible result of the original methods invocation
      * @throws OPMException Problems with plc / proxying
      */
     @SuppressWarnings("unused")
     @RuntimeType
-    public Object intercept(@This Object proxy, @Origin Method method, @SuperCall Callable<?> callable, @Super Object entity) throws OPMException {
-        LOGGER.trace("Invoked method {} on connected PlcEntity {}", method.getName(), entity);
-
-        // Fetch connection from internal variable
-        String address = OpmUtils.extractAddress(proxy);
+    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) 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
+        if (driverManager == null) {
+            LOGGER.trace("Entity not connected, simply fowarding call");
+            try {
+                return callable.call();
+            } catch (Exception e) {
+                throw new OPMException("Exception during forwarding call", e);
+            }
+        }
 
         if (method.getName().startsWith("get")) {
             if (method.getParameterCount() > 0) {
@@ -217,7 +101,7 @@ public class PlcEntityManager {
             // Fetch single value
             LOGGER.trace("Invoked method {} is getter, trying to find annotated field and return requested value",
                 method.getName());
-            return fetchValueForGetter(method, address);
+            return fetchValueForGetter(method, driverManager, address);
         }
 
         if (method.getName().startsWith("is") && (method.getReturnType() == boolean.class || method.getReturnType() == Boolean.class)) {
@@ -227,13 +111,13 @@ public class PlcEntityManager {
             // Fetch single value
             LOGGER.trace("Invoked method {} is boolean flag method, trying to find annotated field and return requested value",
                 method.getName());
-            return fetchValueForIsGetter(method, address);
+            return fetchValueForIsGetter(method, driverManager, address);
         }
 
         // 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, address);
+            refetchAllFields(proxy, driverManager, address);
             return callable.call();
         } catch (Exception e) {
             throw new OPMException("Unable to forward invocation " + method.getName() + " on connected PlcEntity", e);
@@ -244,9 +128,10 @@ public class PlcEntityManager {
      * Renews all values of all Fields that are annotated with {@link PlcEntity}.
      *
      * @param proxy Object to refresh the fields on.
+     * @param driverManager
      * @throws OPMException on various errors.
      */
-    private void refetchAllFields(Object proxy, String address) throws OPMException {
+    private static void refetchAllFields(Object proxy, PlcDriverManager driverManager, String address) 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();
         PlcEntity plcEntity = entityClass.getAnnotation(PlcEntity.class);
@@ -289,16 +174,15 @@ public class PlcEntityManager {
         }
     }
 
-
-    private Object fetchValueForIsGetter(Method m, String address) throws OPMException {
-        return fetchValueForGetter(m, 2, address);
+    private static Object fetchValueForIsGetter(Method m, PlcDriverManager driverManager, String address) throws OPMException {
+        return fetchValueForGetter(m, 2, driverManager, address);
     }
 
-    private Object fetchValueForGetter(Method m, String address) throws OPMException {
-        return fetchValueForGetter(m, 3, address);
+    private static Object fetchValueForGetter(Method m, PlcDriverManager driverManager, String address) throws OPMException {
+        return fetchValueForGetter(m, 3, driverManager, address);
     }
 
-    private Object fetchValueForGetter(Method m, int prefixLength, String address) throws OPMException {
+    private static Object fetchValueForGetter(Method m, int prefixLength, PlcDriverManager driverManager, String address) throws OPMException {
         String s = m.getName().substring(prefixLength);
         // First char to lower
         String variable = s.substring(0, 1).toLowerCase().concat(s.substring(1));
@@ -339,7 +223,7 @@ public class PlcEntityManager {
      * @throws NoSuchFieldException
      * @throws IllegalAccessException
      */
-    private void setField(Class<?> clazz, Object o, PlcReadResponse response, String targetFieldName, String sourceFieldName) throws NoSuchFieldException, IllegalAccessException {
+    static void setField(Class<?> clazz, Object o, PlcReadResponse response, String targetFieldName, String sourceFieldName) throws NoSuchFieldException, IllegalAccessException {
         LOGGER.debug("setField on clazz: {}, Object: {}, response: {}, targetFieldName: {}, sourceFieldName:{} ", clazz, o, response, targetFieldName, sourceFieldName);
         Field field = clazz.getDeclaredField(targetFieldName);
         field.setAccessible(true);
@@ -351,7 +235,7 @@ public class PlcEntityManager {
         }
     }
 
-    private Object getTyped(Class<?> clazz, PlcReadResponse response, String sourceFieldName) {
+    private static Object getTyped(Class<?> clazz, PlcReadResponse response, String sourceFieldName) {
         LOGGER.debug("getTyped clazz: {}, response: {}, fieldName: {}", clazz, response, sourceFieldName);
         if (clazz.isPrimitive()) {
             if (clazz == boolean.class) {
@@ -416,7 +300,7 @@ public class PlcEntityManager {
      * @return the response from the exception.
      * @throws OPMException on {@link InterruptedException} or {@link ExecutionException} or {@link TimeoutException}
      */
-    private PlcReadResponse getPlcReadResponse(PlcReadRequest request) throws OPMException {
+    static PlcReadResponse getPlcReadResponse(PlcReadRequest request) throws OPMException {
         try {
             return request.execute().get(READ_TIMEOUT, TimeUnit.MILLISECONDS);
         } catch (InterruptedException e) {
@@ -428,5 +312,4 @@ public class PlcEntityManager {
             throw new OPMException("Timeout during fetching values", 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 d6d6de6..aeb7fa2 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,34 +22,23 @@ package org.apache.plc4x.java.opm;
 import net.bytebuddy.ByteBuddy;
 import net.bytebuddy.description.modifier.Visibility;
 import net.bytebuddy.implementation.MethodDelegation;
-import net.bytebuddy.implementation.bind.annotation.*;
 import org.apache.commons.configuration2.Configuration;
 import org.apache.commons.configuration2.SystemConfiguration;
-import org.apache.commons.lang3.ArrayUtils;
 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.exceptions.PlcRuntimeException;
 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.math.BigDecimal;
-import java.math.BigInteger;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.LocalTime;
 import java.util.Arrays;
 import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 
 import static net.bytebuddy.matcher.ElementMatchers.any;
 
@@ -82,15 +71,16 @@ import static net.bytebuddy.matcher.ElementMatchers.any;
  * Thus, all operations on fields that are annotated with {@link PlcField} are always done against the "live" values
  * from the PLC.
  * <p>
+ * All invocations on the getters are forwarded to the {@link PlcEntityInterceptor#intercept(Object, Method, Callable, Object)}
+ * method.
  * // TODO Add detach method
  */
 public class PlcEntityManager {
 
-    public static final String PLC_ADDRESS_FIELD_NAME = "_plcAddress";
     private static final Logger LOGGER = LoggerFactory.getLogger(PlcEntityManager.class);
 
-    private static final Configuration CONF = new SystemConfiguration();
-    private static final long READ_TIMEOUT = CONF.getLong("org.apache.plc4x.java.opm.entity_manager.read_timeout", 1_000);
+    public static final String PLC_ADDRESS_FIELD_NAME = "_plcAddress";
+    static final String DRIVER_MANAGER_FIELD_NAME = "_driverManager";
 
     private final PlcDriverManager driverManager;
 
@@ -125,7 +115,7 @@ public class PlcEntityManager {
             PlcReadRequest request = requestBuilder.build();
 
             // Perform the request
-            PlcReadResponse response = getPlcReadResponse(request);
+            PlcReadResponse response = PlcEntityInterceptor.getPlcReadResponse(request);
 
             // Construct the Object
             T instance = clazz.getConstructor().newInstance();
@@ -133,7 +123,7 @@ public class PlcEntityManager {
             // Fill all requested fields
             for (String fieldName : response.getFieldNames()) {
                 String targetFieldName = StringUtils.substringAfterLast(fieldName, ".");
-                setField(clazz, instance, response, targetFieldName, fieldName);
+                PlcEntityInterceptor.setField(clazz, instance, response, targetFieldName, fieldName);
             }
             return instance;
         } catch (PlcInvalidFieldException e) {
@@ -163,269 +153,19 @@ public class PlcEntityManager {
             T instance = new ByteBuddy()
                 .subclass(clazz)
                 .defineField(PLC_ADDRESS_FIELD_NAME, String.class, Visibility.PRIVATE)
-                .method(any()).intercept(MethodDelegation.to(this))
+                .defineField(DRIVER_MANAGER_FIELD_NAME, PlcDriverManager.class, Visibility.PRIVATE)
+                .method(any()).intercept(MethodDelegation.to(PlcEntityInterceptor.class))
                 .make()
                 .load(Thread.currentThread().getContextClassLoader())
                 .getLoaded()
                 .getConstructor()
                 .newInstance();
             // Set connection value into the private field
-            Field plcAddress = instance.getClass().getDeclaredField(PLC_ADDRESS_FIELD_NAME);
-            plcAddress.setAccessible(true);
-            plcAddress.set(instance, address);
+            FieldUtils.writeDeclaredField(instance, PLC_ADDRESS_FIELD_NAME, address, true);
+            FieldUtils.writeDeclaredField(instance, DRIVER_MANAGER_FIELD_NAME, driverManager, true);
             return instance;
         } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
             throw new OPMException("Unable to instantiate Proxy", e);
-        } catch (NoSuchFieldException e) {
-            throw new IllegalStateException("Problem with field injection during proxy generation", e);
-        }
-    }
-
-    //------------------------------------------------------------------------------------------------
-    //
-    //  Methods for interception for the proxy object
-    //
-    //------------------------------------------------------------------------------------------------
-
-    /**
-     * 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(Method, String)} method.
-     * <p>
-     * If the field is no getter, then all fields are refreshed by calling {@link #refetchAllFields(Object,String)}
-     * 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 entity   Reference to the PlcEntity
-     * @return possible result of the original methods invocation
-     * @throws OPMException Problems with plc / proxying
-     */
-    @SuppressWarnings("unused")
-    @RuntimeType
-    public Object intercept(@This Object proxy, @Origin Method method, @SuperCall Callable<?> callable, @Super Object entity) throws OPMException {
-        LOGGER.trace("Invoked method {} on connected PlcEntity {}", method.getName(), entity);
-
-        // Fetch connection from internal variable
-        String address = OpmUtils.extractAddress(proxy);
-
-        if (method.getName().startsWith("get")) {
-            if (method.getParameterCount() > 0) {
-                throw new OPMException("Only getter with no arguments are supported");
-            }
-            // Fetch single value
-            LOGGER.trace("Invoked method {} is getter, trying to find annotated field and return requested value",
-                method.getName());
-            return fetchValueForGetter(method, address);
-        }
-
-        if (method.getName().startsWith("is") && (method.getReturnType() == boolean.class || method.getReturnType() == Boolean.class)) {
-            if (method.getParameterCount() > 0) {
-                throw new OPMException("Only getter with no arguments are supported");
-            }
-            // Fetch single value
-            LOGGER.trace("Invoked method {} is boolean flag method, trying to find annotated field and return requested value",
-                method.getName());
-            return fetchValueForIsGetter(method, address);
-        }
-
-        // 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, address);
-            return callable.call();
-        } catch (Exception e) {
-            throw new OPMException("Unable to forward invocation " + method.getName() + " on connected PlcEntity", e);
-        }
-    }
-
-    /**
-     * Renews all values of all Fields that are annotated with {@link PlcEntity}.
-     *
-     * @param proxy Object to refresh the fields on.
-     * @throws OPMException on various errors.
-     */
-    private void refetchAllFields(Object proxy, String address) 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();
-        PlcEntity plcEntity = entityClass.getAnnotation(PlcEntity.class);
-        if (plcEntity == null) {
-            throw new OPMException("Non PlcEntity supplied");
-        }
-
-        try (PlcConnection connection = driverManager.getConnection(address)) {
-            // Catch the exception, if no reader present (see below)
-            // Build the query
-            PlcReadRequest.Builder requestBuilder = connection.readRequestBuilder();
-
-            Arrays.stream(entityClass.getDeclaredFields())
-                .filter(field -> field.isAnnotationPresent(PlcField.class))
-                .forEach(field ->
-                    requestBuilder.addItem(
-                        field.getDeclaringClass().getName() + "." + field.getName(),
-                        field.getAnnotation(PlcField.class).value()
-                    )
-                );
-
-            PlcReadRequest request = requestBuilder.build();
-
-            PlcReadResponse response = getPlcReadResponse(request);
-
-            // Fill all requested fields
-            for (String fieldName : response.getFieldNames()) {
-                LOGGER.trace("Value for field " + fieldName + " is " + response.getObject(fieldName));
-                String clazzFieldName = StringUtils.substringAfterLast(fieldName, ".");
-                try {
-                    setField(entityClass, proxy, response, clazzFieldName, fieldName);
-                } catch (NoSuchFieldException | IllegalAccessException e) {
-                    throw new PlcRuntimeException(e);
-                }
-            }
-        } catch (PlcConnectionException e) {
-            throw new OPMException("Problem during processing", e);
-        } catch (Exception e) {
-            throw new OPMException("Unknown Error", e);
-        }
-    }
-
-
-    private Object fetchValueForIsGetter(Method m, String address) throws OPMException {
-        return fetchValueForGetter(m, 2, address);
-    }
-
-    private Object fetchValueForGetter(Method m, String address) throws OPMException {
-        return fetchValueForGetter(m, 3, address);
-    }
-
-    private Object fetchValueForGetter(Method m, int prefixLength, String address) throws OPMException {
-        String s = m.getName().substring(prefixLength);
-        // 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;
-        try {
-            annotation = m.getDeclaringClass().getDeclaredField(variable).getDeclaredAnnotation(PlcField.class);
-        } catch (NoSuchFieldException e) {
-            throw new OPMException("Unable to identify field annotated field for call to " + m.getName(), e);
-        }
-        try (PlcConnection connection = driverManager.getConnection(address)) {
-            // Catch the exception, if no reader present (see below)
-
-            // Assume to do the query here...
-            PlcReadRequest request = connection.readRequestBuilder()
-                .addItem(m.getName(), annotation.value())
-                .build();
-
-            PlcReadResponse response = getPlcReadResponse(request);
-
-            return getTyped(m.getReturnType(), response, m.getName());
-        } catch (ClassCastException e) {
-            throw new OPMException("Unable to return response as suitable type", e);
-        } catch (Exception e) {
-            throw new OPMException("Problem during processing", e);
-        }
-    }
-
-    /**
-     * 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
-     * which is equal to the given fieldName parameter.
-     *
-     * @param o               Object to set the value on
-     * @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
-     * @throws IllegalAccessException
-     */
-    private void setField(Class<?> clazz, Object o, PlcReadResponse response, String targetFieldName, String sourceFieldName) throws NoSuchFieldException, IllegalAccessException {
-        LOGGER.debug("setField on clazz: {}, Object: {}, response: {}, targetFieldName: {}, sourceFieldName:{} ", clazz, o, response, targetFieldName, sourceFieldName);
-        Field field = clazz.getDeclaredField(targetFieldName);
-        field.setAccessible(true);
-        try {
-            field.set(o, getTyped(field.getType(), response, sourceFieldName));
-        } catch (ClassCastException e) {
-            // TODO should we simply fail here?
-            LOGGER.warn("Unable to assign return value {} to field {} with type {}", response.getObject(sourceFieldName), targetFieldName, field.getType(), e);
-        }
-    }
-
-    private Object getTyped(Class<?> clazz, PlcReadResponse response, String sourceFieldName) {
-        LOGGER.debug("getTyped clazz: {}, response: {}, fieldName: {}", clazz, response, sourceFieldName);
-        if (clazz.isPrimitive()) {
-            if (clazz == boolean.class) {
-                return response.getBoolean(sourceFieldName);
-            } else if (clazz == byte.class) {
-                return response.getByte(sourceFieldName);
-            } else if (clazz == short.class) {
-                return response.getShort(sourceFieldName);
-            } else if (clazz == int.class) {
-                return response.getInteger(sourceFieldName);
-            } else if (clazz == long.class) {
-                return response.getLong(sourceFieldName);
-            }
-        }
-
-        if (clazz == Boolean.class) {
-            return response.getBoolean(sourceFieldName);
-        } else if (clazz == Byte.class) {
-            return response.getByte(sourceFieldName);
-        } else if (clazz == Short.class) {
-            return response.getShort(sourceFieldName);
-        } else if (clazz == Integer.class) {
-            return response.getInteger(sourceFieldName);
-        } else if (clazz == Long.class) {
-            return response.getLong(sourceFieldName);
-        } else if (clazz == BigInteger.class) {
-            return response.getBigInteger(sourceFieldName);
-        } else if (clazz == Float.class) {
-            return response.getFloat(sourceFieldName);
-        } else if (clazz == Double.class) {
-            return response.getDouble(sourceFieldName);
-        } else if (clazz == BigDecimal.class) {
-            return response.getBigDecimal(sourceFieldName);
-        } else if (clazz == String.class) {
-            return response.getString(sourceFieldName);
-        } else if (clazz == LocalTime.class) {
-            return response.getTime(sourceFieldName);
-        } else if (clazz == LocalDate.class) {
-            return response.getDate(sourceFieldName);
-        } else if (clazz == LocalDateTime.class) {
-            return response.getDateTime(sourceFieldName);
-        } else if (clazz == byte[].class) {
-            return ArrayUtils.toPrimitive(response.getByteArray(sourceFieldName));
-        } else if (clazz == Byte[].class) {
-            return response.getByteArray(sourceFieldName);
-        }
-
-        // Fallback
-        Object responseObject = response.getObject(sourceFieldName);
-        if (clazz.isAssignableFrom(responseObject.getClass())) {
-            return responseObject;
-        }
-
-        // If nothing matched, throw
-        throw new ClassCastException("Unable to return response item " + responseObject + "(" + responseObject.getClass() + ") as instance of " + clazz);
-    }
-
-    /**
-     * 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}
-     */
-    private PlcReadResponse getPlcReadResponse(PlcReadRequest request) throws OPMException {
-        try {
-            return request.execute().get(READ_TIMEOUT, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            throw new OPMException("Exception during execution", e);
-        } catch (ExecutionException e) {
-            throw new OPMException("Exception during execution", e);
-        } catch (TimeoutException e) {
-            throw new OPMException("Timeout during fetching values", e);
         }
     }
 


[incubator-plc4x] 02/02: [OPM] Added Documentation, some refactoring.

Posted by jf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jfeinauer pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git

commit a5ae69baf1deed89ac79aeb2b152fc36e8c804e2
Author: Julian Feinauer <j....@pragmaticminds.de>
AuthorDate: Sat Oct 27 13:20:53 2018 +0200

    [OPM] Added Documentation, some refactoring.
---
 .../plc4x/java/opm/PlcEntityInterceptor.java       | 16 ++--
 .../apache/plc4x/java/opm/PlcEntityManager.java    | 32 +++++++-
 .../java/org/apache/plc4x/java/opm/PlcField.java   |  3 +
 .../utils/opm/src/site/asciidoc/opm/using-opm.adoc | 61 +++++++++++++++
 plc4j/utils/opm/src/site/site.xml                  | 30 ++++++++
 .../plc4x/java/opm/PlcEntityManagerTest.java       | 87 ++++++++++++----------
 6 files changed, 183 insertions(+), 46 deletions(-)

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 48df70e..81d6126 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
@@ -74,6 +74,8 @@ public class PlcEntityInterceptor {
      * @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
      */
@@ -131,7 +133,7 @@ public class PlcEntityInterceptor {
      * @param driverManager
      * @throws OPMException on various errors.
      */
-    private static void refetchAllFields(Object proxy, PlcDriverManager driverManager, String address) throws OPMException {
+    static void refetchAllFields(Object proxy, PlcDriverManager driverManager, String address) 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();
         PlcEntity plcEntity = entityClass.getAnnotation(PlcEntity.class);
@@ -188,22 +190,26 @@ public class PlcEntityInterceptor {
         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 {
-            annotation = m.getDeclaringClass().getDeclaredField(variable).getDeclaredAnnotation(PlcField.class);
+            field = m.getDeclaringClass().getDeclaredField(variable);
+            annotation = field.getDeclaredAnnotation(PlcField.class);
         } catch (NoSuchFieldException e) {
             throw new OPMException("Unable to identify field annotated field for call to " + m.getName(), e);
         }
         try (PlcConnection connection = driverManager.getConnection(address)) {
             // Catch the exception, if no reader present (see below)
 
-            // Assume to do the query here...
+            // Use Fully qualified Name as field index
+            String fqn = field.getDeclaringClass().getName() + "." + field.getName();
+
             PlcReadRequest request = connection.readRequestBuilder()
-                .addItem(m.getName(), annotation.value())
+                .addItem(fqn, annotation.value())
                 .build();
 
             PlcReadResponse response = getPlcReadResponse(request);
 
-            return getTyped(m.getReturnType(), response, m.getName());
+            return getTyped(m.getReturnType(), response, fqn);
         } catch (ClassCastException e) {
             throw new OPMException("Unable to return response as suitable type", e);
         } catch (Exception 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 aeb7fa2..7544b29 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
@@ -71,9 +71,11 @@ import static net.bytebuddy.matcher.ElementMatchers.any;
  * Thus, all operations on fields that are annotated with {@link PlcField} are always done against the "live" values
  * from the PLC.
  * <p>
- * All invocations on the getters are forwarded to the {@link PlcEntityInterceptor#intercept(Object, Method, Callable, Object)}
+ * A connected @{@link PlcEntity} can be disconnected calling {@link #disconnect(Object)}, then it behaves like the
+ * regular Pojo it was before.
+ * <p>
+ * All invocations on the getters are forwarded to the {@link PlcEntityInterceptor#intercept(Object, Method, Callable, String, PlcDriverManager)}
  * method.
- * // TODO Add detach method
  */
 public class PlcEntityManager {
 
@@ -163,10 +165,36 @@ public class PlcEntityManager {
             // Set connection value into the private field
             FieldUtils.writeDeclaredField(instance, PLC_ADDRESS_FIELD_NAME, address, true);
             FieldUtils.writeDeclaredField(instance, DRIVER_MANAGER_FIELD_NAME, driverManager, true);
+
+            // Initially fetch all values
+            PlcEntityInterceptor.refetchAllFields(instance, driverManager, address);
+
             return instance;
         } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
             throw new OPMException("Unable to instantiate Proxy", e);
         }
     }
 
+    /**
+     * Disconnects the given instance.
+     * @param entity Instance of a PlcEntity.
+     * @throws OPMException Is thrown when the plc is already disconnected or no entity.
+     */
+    public void disconnect(Object entity) throws OPMException {
+        // Check if this is an entity
+        PlcEntity annotation = entity.getClass().getSuperclass().getAnnotation(PlcEntity.class);
+        if (annotation == null) {
+            throw new OPMException("Unable to disconnect Object, is no entity!");
+        }
+        try {
+            Object manager = FieldUtils.readDeclaredField(entity, DRIVER_MANAGER_FIELD_NAME, true);
+            if (manager == null) {
+                throw new OPMException("Instance is already disconnected!");
+            }
+            FieldUtils.writeDeclaredField(entity, DRIVER_MANAGER_FIELD_NAME, null, true);
+        } catch (IllegalAccessException e) {
+            throw new OPMException("Unbale to fetch driverManager instance on entity instance", e);
+        }
+    }
+
 }
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 9dd132e..cd0474e 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
@@ -31,4 +31,7 @@ import java.lang.annotation.Target;
 @Target({ElementType.METHOD, ElementType.FIELD})
 public @interface PlcField {
     String value();
+    // TODO enable both annotation values in the Interceptor / Entitymanager
+    long cacheDurationMillis() default 1000;
+    boolean throwOnUnavailable() default true;
 }
diff --git a/plc4j/utils/opm/src/site/asciidoc/opm/using-opm.adoc b/plc4j/utils/opm/src/site/asciidoc/opm/using-opm.adoc
new file mode 100644
index 0000000..1eb9920
--- /dev/null
+++ b/plc4j/utils/opm/src/site/asciidoc/opm/using-opm.adoc
@@ -0,0 +1,61 @@
+//
+//  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.
+//
+
+== What is Object Plc Mapping (OPM)?
+
+ASDF:
+
+....
+    public class ModbusTcpPlcConnection extends BaseModbusPlcConnection {
+
+        private static final int MODBUS_TCP_PORT = 502;
+
+        public ModbusTcpPlcConnection(InetAddress address, String params) {
+            this(new TcpSocketChannelFactory(address, MODBUS_TCP_PORT), params);
+            logger.info("Configured ModbusTcpPlcConnection with: host-name {}", address.getHostAddress());
+        }
+
+        ModbusTcpPlcConnection(ChannelFactory channelFactory, String params) {
+            super(channelFactory, params);
+        }
+
+        @Override
+        protected ChannelHandler getChannelHandler(CompletableFuture<Void> sessionSetupCompleteFuture) {
+            return new ChannelInitializer() {
+                @Override
+                protected void initChannel(Channel channel) {
+                    // Build the protocol stack for communicating with the modbus protocol.
+                    ChannelPipeline pipeline = channel.pipeline();
+                    pipeline.addLast(new ModbusTcpProtocol());
+                    pipeline.addLast(new ModbusProtocol());
+                    pipeline.addLast(new Plc4XModbusProtocol());
+                }
+            };
+        }
+
+    }
+....
+
+As you can see in above example there are two constructors.
+The first one is the default, which establishes a connection using the default connector.
+As the TCP variant of the `Modbus` protocol uses normal TCP, a `TcpSocketChannelFactory` instance is used.
+However in order to test the driver, a unit- or integration-test can use the second constructor to inject a different `ChannelFactory`.
+Notice that this constructor can be package-private if the test-case is in the same package.
+Here the `TestConnectionFactory` will allow creating tests without having to worry about the physical connection and all problems that come with it.
+
+The pipeline itself is created in the `getChannelHandler` method.
+Here you have to keep in mind that the layer that is closest to the connection has to be added first, the `PLC4X Layer` last.
diff --git a/plc4j/utils/opm/src/site/site.xml b/plc4j/utils/opm/src/site/site.xml
new file mode 100644
index 0000000..e250730
--- /dev/null
+++ b/plc4j/utils/opm/src/site/site.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+  ~ 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.
+  -->
+<project>
+
+  <body>
+    <menu name="Users">
+    </menu>
+    <menu name="Developers">
+      <item name="Object Plc Mapping (OPM)" href="opm/using-opm.html"/>
+    </menu>
+  </body>
+
+</project>
\ No newline at end of file
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 8017d9c..19772c5 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
@@ -19,6 +19,7 @@
 
 package org.apache.plc4x.java.opm;
 
+import org.apache.commons.lang3.reflect.FieldUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.plc4x.java.PlcDriverManager;
 import org.apache.plc4x.java.api.PlcConnection;
@@ -51,6 +52,8 @@ import java.util.function.Function;
 import java.util.stream.Collectors;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.mockito.Mockito.when;
 
 
@@ -88,29 +91,7 @@ public class PlcEntityManagerTest {
 
     @Test
     public void readComplexObject() throws PlcConnectionException, OPMException {
-        Map<String, BaseDefaultFieldItem> map = new HashMap<>();
-        String prefix = ConnectedEntity.class.getName() + ".";
-        map.put(prefix + "boolVar", new DefaultBooleanFieldItem(true));
-        map.put(prefix + "byteVar", new DefaultByteFieldItem((byte) 1));
-        map.put(prefix + "shortVar", new DefaultShortFieldItem((short) 1));
-        map.put(prefix + "intVar", new DefaultIntegerFieldItem(1));
-        map.put(prefix + "longVar", new DefaultLongFieldItem(1l));
-        map.put(prefix + "boxedBoolVar", new DefaultLongFieldItem(1L));
-        map.put(prefix + "boxedByteVar", new DefaultByteFieldItem((byte) 1));
-        map.put(prefix + "boxedShortVar", new DefaultShortFieldItem((short) 1));
-        map.put(prefix + "boxedIntegerVar", new DefaultIntegerFieldItem(1));
-        map.put(prefix + "boxedLongVar", new DefaultLongFieldItem(1l));
-        map.put(prefix + "bigIntegerVar", new DefaultBigIntegerFieldItem(BigInteger.ONE));
-        map.put(prefix + "floatVar", new DefaultFloatFieldItem(1f));
-        map.put(prefix + "doubleVar", new DefaultDoubleFieldItem(1d));
-        map.put(prefix + "bigDecimalVar", new DefaultBigDecimalFieldItem(BigDecimal.ONE));
-        map.put(prefix + "localTimeVar", new DefaultLocalTimeFieldItem(LocalTime.of(1, 1)));
-        map.put(prefix + "localDateVar", new DefaultLocalDateFieldItem(LocalDate.of(1, 1, 1)));
-        map.put(prefix + "localDateTimeVar", new DefaultLocalDateTimeFieldItem(LocalDateTime.of(1, 1, 1, 1, 1)));
-        map.put(prefix + "byteArrayVar", new DefaultByteArrayFieldItem(new Byte[]{0x0, 0x1}));
-        map.put(prefix + "bigByteArrayVar", new DefaultByteArrayFieldItem(new Byte[]{0x0, 0x1}));
-        map.put(prefix + "stringVar", new DefaultStringFieldItem("Hallo"));
-        PlcEntityManager manager = getPlcEntityManager(map);
+        PlcEntityManager manager = getInitializedEntityManager();
 
         ConnectedEntity connect = manager.read(ConnectedEntity.class, "s7://localhost:5555/0/0");
 
@@ -124,6 +105,19 @@ public class PlcEntityManagerTest {
 
     @Test
     public void connect_callComplexMethod() throws PlcConnectionException, OPMException {
+        PlcEntityManager manager = getInitializedEntityManager();
+
+        ConnectedEntity connect = manager.connect(ConnectedEntity.class, "s7://localhost:5555/0/0");
+
+        Assert.assertNotNull(connect);
+
+        // Call different mehtod
+        String s = connect.toString();
+
+        assertEquals("ConnectedEntity{boolVar=true, byteVar=1, shortVar=1, intVar=1, longVar=1, boxedBoolVar=true, boxedByteVar=1, boxedShortVar=1, boxedIntegerVar=1, boxedLongVar=1, bigIntegerVar=1, floatVar=1.0, doubleVar=1.0, bigDecimalVar=1, localTimeVar=01:01, localDateVar=0001-01-01, localDateTimeVar=0001-01-01T01:01, byteArrayVar=[0, 1], bigByteArrayVar=[0, 1], stringVar='Hallo'}", s);
+    }
+
+    private PlcEntityManager getInitializedEntityManager() throws PlcConnectionException {
         Map<String, BaseDefaultFieldItem> map = new HashMap<>();
         String prefix = ConnectedEntity.class.getName() + ".";
         map.put(prefix + "boolVar", new DefaultBooleanFieldItem(true));
@@ -146,25 +140,12 @@ public class PlcEntityManagerTest {
         map.put(prefix + "byteArrayVar", new DefaultByteArrayFieldItem(new Byte[]{0x0, 0x1}));
         map.put(prefix + "bigByteArrayVar", new DefaultByteArrayFieldItem(new Byte[]{0x0, 0x1}));
         map.put(prefix + "stringVar", new DefaultStringFieldItem("Hallo"));
-        PlcEntityManager manager = getPlcEntityManager(map);
-
-        ConnectedEntity connect = manager.connect(ConnectedEntity.class, "s7://localhost:5555/0/0");
-
-        Assert.assertNotNull(connect);
-
-        // Call different mehtod
-        String s = connect.toString();
-
-        assertEquals("ConnectedEntity{boolVar=true, byteVar=1, shortVar=1, intVar=1, longVar=1, boxedBoolVar=true, boxedByteVar=1, boxedShortVar=1, boxedIntegerVar=1, boxedLongVar=1, bigIntegerVar=1, floatVar=1.0, doubleVar=1.0, bigDecimalVar=1, localTimeVar=01:01, localDateVar=0001-01-01, localDateTimeVar=0001-01-01T01:01, byteArrayVar=[0, 1], bigByteArrayVar=[0, 1], stringVar='Hallo'}", s);
+        return getPlcEntityManager(map);
     }
 
     @Test
     public void connect_callGetter() throws PlcConnectionException, OPMException {
-        Map<String, BaseDefaultFieldItem> map = new HashMap<>();
-        map.put("getIntVar", new DefaultIntegerFieldItem(1));
-        map.put("getStringVar", new DefaultStringFieldItem("Hello"));
-        map.put("isBoolVar", new DefaultBooleanFieldItem(true));
-        PlcEntityManager manager = getPlcEntityManager(map);
+        PlcEntityManager manager = getInitializedEntityManager();
 
         ConnectedEntity connect = manager.connect(ConnectedEntity.class, "s7://localhost:5555/0/0");
 
@@ -172,10 +153,38 @@ public class PlcEntityManagerTest {
 
         // Call getter
         assertEquals(1, connect.getIntVar());
-        assertEquals("Hello", connect.getStringVar());
+        assertEquals("Hallo", connect.getStringVar());
         assertEquals(true, connect.isBoolVar());
     }
 
+    @Test
+    public void disconnect() throws PlcConnectionException, OPMException, IllegalAccessException {
+        PlcEntityManager manager = getInitializedEntityManager();
+
+        ConnectedEntity connected = manager.connect(ConnectedEntity.class, "s7://localhost:5555/0/0");
+
+        manager.disconnect(connected);
+
+        // Assert disconnected
+        Object o = FieldUtils.readDeclaredField(connected, PlcEntityManager.DRIVER_MANAGER_FIELD_NAME, true);
+        assertNull(o);
+
+        // Call a method and receive the result
+        // We are ok if a result is received and no NPE is thrown, then everything works as expected
+        assertNotNull(connected.toString());
+        assertNotNull(connected.getByteVar());
+    }
+
+    @Test(expected = OPMException.class)
+    public void disconnectTwice_throwsException() throws PlcConnectionException, OPMException {
+        PlcEntityManager manager = getPlcEntityManager(new HashMap<>());
+
+        ConnectedEntity connected = manager.connect(ConnectedEntity.class, "s7://localhost:5555/0/0");
+
+        manager.disconnect(connected);
+        manager.disconnect(connected);
+    }
+
     private PlcEntityManager getPlcEntityManager(final Map<String, BaseDefaultFieldItem> responses) throws PlcConnectionException {
         driverManager = Mockito.mock(PlcDriverManager.class);
         PlcDriverManager mock = driverManager;