Option to ignore SNMP response type cast errors
This commit is contained in:
		
							parent
							
								
									09b866c41f
								
							
						
					
					
						commit
						854e059435
					
				@ -22,6 +22,7 @@ import com.google.gson.JsonPrimitive;
 | 
				
			|||||||
import lombok.Data;
 | 
					import lombok.Data;
 | 
				
			||||||
import lombok.SneakyThrows;
 | 
					import lombok.SneakyThrows;
 | 
				
			||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
 | 
					import org.apache.commons.lang3.exception.ExceptionUtils;
 | 
				
			||||||
 | 
					import org.apache.commons.lang3.tuple.Pair;
 | 
				
			||||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
					import org.springframework.beans.factory.annotation.Autowired;
 | 
				
			||||||
import org.springframework.security.core.context.SecurityContext;
 | 
					import org.springframework.security.core.context.SecurityContext;
 | 
				
			||||||
import org.springframework.security.core.context.SecurityContextHolder;
 | 
					import org.springframework.security.core.context.SecurityContextHolder;
 | 
				
			||||||
@ -57,7 +58,7 @@ import org.thingsboard.server.service.security.permission.Operation;
 | 
				
			|||||||
import org.thingsboard.server.service.security.permission.Resource;
 | 
					import org.thingsboard.server.service.security.permission.Resource;
 | 
				
			||||||
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
 | 
					import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
 | 
				
			||||||
import org.thingsboard.server.utils.CsvUtils;
 | 
					import org.thingsboard.server.utils.CsvUtils;
 | 
				
			||||||
import org.thingsboard.server.utils.TypeCastUtil;
 | 
					import org.thingsboard.server.common.data.util.TypeCastUtil;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.annotation.Nullable;
 | 
					import javax.annotation.Nullable;
 | 
				
			||||||
import javax.annotation.PostConstruct;
 | 
					import javax.annotation.PostConstruct;
 | 
				
			||||||
@ -269,7 +270,7 @@ public abstract class AbstractBulkImportService<E extends HasId<? extends Entity
 | 
				
			|||||||
                                if (!entry.getKey().getType().isKv()) {
 | 
					                                if (!entry.getKey().getType().isKv()) {
 | 
				
			||||||
                                    entityData.getFields().put(entry.getKey().getType(), entry.getValue());
 | 
					                                    entityData.getFields().put(entry.getKey().getType(), entry.getValue());
 | 
				
			||||||
                                } else {
 | 
					                                } else {
 | 
				
			||||||
                                    Map.Entry<DataType, Object> castResult = TypeCastUtil.castValue(entry.getValue());
 | 
					                                    Pair<DataType, Object> castResult = TypeCastUtil.castValue(entry.getValue());
 | 
				
			||||||
                                    entityData.getKvs().put(entry.getKey(), new ParsedValue(castResult.getValue(), castResult.getKey()));
 | 
					                                    entityData.getKvs().put(entry.getKey(), new ParsedValue(castResult.getValue(), castResult.getKey()));
 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
                            });
 | 
					                            });
 | 
				
			||||||
 | 
				
			|||||||
@ -975,6 +975,8 @@ transport:
 | 
				
			|||||||
    # to configure SNMP to work over UDP or TCP
 | 
					    # to configure SNMP to work over UDP or TCP
 | 
				
			||||||
    underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}"
 | 
					    underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}"
 | 
				
			||||||
    max_request_oids: "${SNMP_MAX_REQUEST_OIDS:100}"
 | 
					    max_request_oids: "${SNMP_MAX_REQUEST_OIDS:100}"
 | 
				
			||||||
 | 
					    response:
 | 
				
			||||||
 | 
					      ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}"
 | 
				
			||||||
  stats:
 | 
					  stats:
 | 
				
			||||||
    enabled: "${TB_TRANSPORT_STATS_ENABLED:true}"
 | 
					    enabled: "${TB_TRANSPORT_STATS_ENABLED:true}"
 | 
				
			||||||
    print-interval-ms: "${TB_TRANSPORT_STATS_PRINT_INTERVAL_MS:60000}"
 | 
					    print-interval-ms: "${TB_TRANSPORT_STATS_PRINT_INTERVAL_MS:60000}"
 | 
				
			||||||
 | 
				
			|||||||
@ -163,6 +163,15 @@ public class StringUtils {
 | 
				
			|||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static boolean equalsAnyIgnoreCase(String string, String... otherStrings) {
 | 
				
			||||||
 | 
					        for (String otherString : otherStrings) {
 | 
				
			||||||
 | 
					            if (equalsIgnoreCase(string, otherString)) {
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static String substringAfterLast(String str, String sep) {
 | 
					    public static String substringAfterLast(String str, String sep) {
 | 
				
			||||||
        return org.apache.commons.lang3.StringUtils.substringAfterLast(str, sep);
 | 
					        return org.apache.commons.lang3.StringUtils.substringAfterLast(str, sep);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -13,35 +13,53 @@
 | 
				
			|||||||
 * See the License for the specific language governing permissions and
 | 
					 * See the License for the specific language governing permissions and
 | 
				
			||||||
 * limitations under the License.
 | 
					 * limitations under the License.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
package org.thingsboard.server.utils;
 | 
					package org.thingsboard.server.common.data.util;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.apache.commons.lang3.math.NumberUtils;
 | 
					import org.apache.commons.lang3.math.NumberUtils;
 | 
				
			||||||
 | 
					import org.apache.commons.lang3.tuple.Pair;
 | 
				
			||||||
import org.thingsboard.server.common.data.kv.DataType;
 | 
					import org.thingsboard.server.common.data.kv.DataType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.math.BigDecimal;
 | 
					import java.math.BigDecimal;
 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class TypeCastUtil {
 | 
					public class TypeCastUtil {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private TypeCastUtil() {}
 | 
					    private TypeCastUtil() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static Map.Entry<DataType, Object> castValue(String value) {
 | 
					    public static Pair<DataType, Object> castValue(String value) {
 | 
				
			||||||
        if (isNumber(value)) {
 | 
					        if (isNumber(value)) {
 | 
				
			||||||
            String formattedValue = value.replace(',', '.');
 | 
					            String formattedValue = value.replace(',', '.');
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                BigDecimal bd = new BigDecimal(formattedValue);
 | 
					                BigDecimal bd = new BigDecimal(formattedValue);
 | 
				
			||||||
                if (bd.stripTrailingZeros().scale() > 0 || isSimpleDouble(formattedValue)) {
 | 
					                if (bd.stripTrailingZeros().scale() > 0 || isSimpleDouble(formattedValue)) {
 | 
				
			||||||
                    if (bd.scale() <= 16) {
 | 
					                    if (bd.scale() <= 16) {
 | 
				
			||||||
                        return Map.entry(DataType.DOUBLE, bd.doubleValue());
 | 
					                        return Pair.of(DataType.DOUBLE, bd.doubleValue());
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    return Map.entry(DataType.LONG, bd.longValueExact());
 | 
					                    return Pair.of(DataType.LONG, bd.longValueExact());
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } catch (RuntimeException ignored) {}
 | 
					            } catch (RuntimeException ignored) {}
 | 
				
			||||||
        } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
 | 
					        } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
 | 
				
			||||||
            return Map.entry(DataType.BOOLEAN, Boolean.parseBoolean(value));
 | 
					            return Pair.of(DataType.BOOLEAN, Boolean.parseBoolean(value));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return Pair.of(DataType.STRING, value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static Pair<DataType, Number> castToNumber(String value) {
 | 
				
			||||||
 | 
					        if (isNumber(value)) {
 | 
				
			||||||
 | 
					            String formattedValue = value.replace(',', '.');
 | 
				
			||||||
 | 
					            BigDecimal bd = new BigDecimal(formattedValue);
 | 
				
			||||||
 | 
					            if (bd.stripTrailingZeros().scale() > 0 || isSimpleDouble(formattedValue)) {
 | 
				
			||||||
 | 
					                if (bd.scale() <= 16) {
 | 
				
			||||||
 | 
					                    return Pair.of(DataType.DOUBLE, bd.doubleValue());
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    return Pair.of(DataType.DOUBLE, bd);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                return Pair.of(DataType.LONG, bd.longValueExact());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            throw new IllegalArgumentException("'" + value + "' can't be parsed as number");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return Map.entry(DataType.STRING, value);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static boolean isNumber(String value) {
 | 
					    private static boolean isNumber(String value) {
 | 
				
			||||||
@ -17,6 +17,7 @@ package org.thingsboard.server.transport.snmp.service;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import com.google.common.collect.Lists;
 | 
					import com.google.common.collect.Lists;
 | 
				
			||||||
import com.google.gson.JsonObject;
 | 
					import com.google.gson.JsonObject;
 | 
				
			||||||
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
import org.snmp4j.PDU;
 | 
					import org.snmp4j.PDU;
 | 
				
			||||||
import org.snmp4j.ScopedPDU;
 | 
					import org.snmp4j.ScopedPDU;
 | 
				
			||||||
@ -28,12 +29,14 @@ import org.snmp4j.smi.Variable;
 | 
				
			|||||||
import org.snmp4j.smi.VariableBinding;
 | 
					import org.snmp4j.smi.VariableBinding;
 | 
				
			||||||
import org.springframework.beans.factory.annotation.Value;
 | 
					import org.springframework.beans.factory.annotation.Value;
 | 
				
			||||||
import org.springframework.stereotype.Service;
 | 
					import org.springframework.stereotype.Service;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.StringUtils;
 | 
				
			||||||
import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration;
 | 
					import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration;
 | 
				
			||||||
import org.thingsboard.server.common.data.kv.DataType;
 | 
					import org.thingsboard.server.common.data.kv.DataType;
 | 
				
			||||||
import org.thingsboard.server.common.data.transport.snmp.SnmpMapping;
 | 
					import org.thingsboard.server.common.data.transport.snmp.SnmpMapping;
 | 
				
			||||||
import org.thingsboard.server.common.data.transport.snmp.SnmpMethod;
 | 
					import org.thingsboard.server.common.data.transport.snmp.SnmpMethod;
 | 
				
			||||||
import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion;
 | 
					import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion;
 | 
				
			||||||
import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig;
 | 
					import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.util.TypeCastUtil;
 | 
				
			||||||
import org.thingsboard.server.queue.util.TbSnmpTransportComponent;
 | 
					import org.thingsboard.server.queue.util.TbSnmpTransportComponent;
 | 
				
			||||||
import org.thingsboard.server.transport.snmp.session.DeviceSessionContext;
 | 
					import org.thingsboard.server.transport.snmp.session.DeviceSessionContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -48,11 +51,15 @@ import java.util.stream.Collectors;
 | 
				
			|||||||
@TbSnmpTransportComponent
 | 
					@TbSnmpTransportComponent
 | 
				
			||||||
@Service
 | 
					@Service
 | 
				
			||||||
@Slf4j
 | 
					@Slf4j
 | 
				
			||||||
 | 
					@RequiredArgsConstructor
 | 
				
			||||||
public class PduService {
 | 
					public class PduService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Value("${transport.snmp.max_request_oids:100}")
 | 
					    @Value("${transport.snmp.max_request_oids:100}")
 | 
				
			||||||
    private int maxRequestOids;
 | 
					    private int maxRequestOids;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Value("${transport.snmp.response.ignore_type_cast_errors:false}")
 | 
				
			||||||
 | 
					    private boolean ignoreTypeCastErrors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public List<PDU> createPdus(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map<String, String> values) {
 | 
					    public List<PDU> createPdus(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map<String, String> values) {
 | 
				
			||||||
        List<PDU> pdus = new ArrayList<>();
 | 
					        List<PDU> pdus = new ArrayList<>();
 | 
				
			||||||
        List<SnmpMapping> allMappings = communicationConfig.getAllMappings();
 | 
					        List<SnmpMapping> allMappings = communicationConfig.getAllMappings();
 | 
				
			||||||
@ -165,20 +172,31 @@ public class PduService {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void processValue(String key, DataType dataType, String value, JsonObject result) {
 | 
					    public void processValue(String key, DataType dataType, String value, JsonObject result) {
 | 
				
			||||||
        switch (dataType) {
 | 
					        try {
 | 
				
			||||||
            case LONG:
 | 
					            switch (dataType) {
 | 
				
			||||||
                result.addProperty(key, Long.parseLong(value));
 | 
					                case STRING:
 | 
				
			||||||
                break;
 | 
					                case JSON:
 | 
				
			||||||
            case BOOLEAN:
 | 
					                    result.addProperty(key, value);
 | 
				
			||||||
                result.addProperty(key, Boolean.parseBoolean(value));
 | 
					                    break;
 | 
				
			||||||
                break;
 | 
					                case LONG:
 | 
				
			||||||
            case DOUBLE:
 | 
					                case DOUBLE:
 | 
				
			||||||
                result.addProperty(key, Double.parseDouble(value));
 | 
					                    result.addProperty(key, TypeCastUtil.castToNumber(value).getValue());
 | 
				
			||||||
                break;
 | 
					                    break;
 | 
				
			||||||
            case STRING:
 | 
					                case BOOLEAN:
 | 
				
			||||||
            case JSON:
 | 
					                    if (StringUtils.equalsAnyIgnoreCase(value, "true", "false")) {
 | 
				
			||||||
            default:
 | 
					                        result.addProperty(key, Boolean.parseBoolean(value));
 | 
				
			||||||
                result.addProperty(key, value);
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        throw new IllegalArgumentException("Can't parse '" + value + "' as boolean");
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (IllegalArgumentException e) {
 | 
				
			||||||
 | 
					            if (ignoreTypeCastErrors) {
 | 
				
			||||||
 | 
					                log.debug("Ignoring value '{}' for key '{}' because of data type mismatch ({} required)", value, key, dataType);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                throw e;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -104,6 +104,8 @@ transport:
 | 
				
			|||||||
    # to configure SNMP to work over UDP or TCP
 | 
					    # to configure SNMP to work over UDP or TCP
 | 
				
			||||||
    underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}"
 | 
					    underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}"
 | 
				
			||||||
    max_request_oids: "${SNMP_MAX_REQUEST_OIDS:100}"
 | 
					    max_request_oids: "${SNMP_MAX_REQUEST_OIDS:100}"
 | 
				
			||||||
 | 
					    response:
 | 
				
			||||||
 | 
					      ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}"
 | 
				
			||||||
  sessions:
 | 
					  sessions:
 | 
				
			||||||
    inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
 | 
					    inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
 | 
				
			||||||
    report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}"
 | 
					    report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}"
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user