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:19 UTC

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

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;