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:18 UTC
[incubator-plc4x] 01/02: [OPM] Refactoring. Added
PlcEntityInterceptor.
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);
}
}