diff options
author | Anton Tananaev <anton@traccar.org> | 2022-06-18 10:00:52 -0700 |
---|---|---|
committer | Anton Tananaev <anton@traccar.org> | 2022-06-18 10:00:52 -0700 |
commit | c248ed30047a0525bf792730a0fbd4de0c89ad8e (patch) | |
tree | 750adc33654a4f8947e09528cfb85534cfceb8f1 | |
parent | 68826cdc767bae6b8b39e88667372f0c6161efa9 (diff) | |
download | trackermap-server-c248ed30047a0525bf792730a0fbd4de0c89ad8e.tar.gz trackermap-server-c248ed30047a0525bf792730a0fbd4de0c89ad8e.tar.bz2 trackermap-server-c248ed30047a0525bf792730a0fbd4de0c89ad8e.zip |
Refactor attribute lookup
19 files changed, 748 insertions, 515 deletions
diff --git a/src/main/java/org/traccar/BaseProtocolDecoder.java b/src/main/java/org/traccar/BaseProtocolDecoder.java index cbcb429b3..076b52e96 100644 --- a/src/main/java/org/traccar/BaseProtocolDecoder.java +++ b/src/main/java/org/traccar/BaseProtocolDecoder.java @@ -23,11 +23,13 @@ import org.traccar.database.IdentityManager; import org.traccar.database.MediaManager; import org.traccar.database.StatisticsManager; import org.traccar.helper.UnitsConverter; +import org.traccar.helper.model.AttributeUtil; import org.traccar.model.Command; import org.traccar.model.Device; import org.traccar.model.Position; import org.traccar.session.ConnectionManager; import org.traccar.session.DeviceSession; +import org.traccar.session.cache.CacheManager; import org.traccar.storage.StorageException; import javax.inject.Inject; @@ -46,6 +48,7 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder { private final Protocol protocol; private IdentityManager identityManager; + private CacheManager cacheManager; private ConnectionManager connectionManager; private StatisticsManager statisticsManager; private MediaManager mediaManager; @@ -64,6 +67,15 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder { this.identityManager = identityManager; } + public CacheManager getCacheManager() { + return cacheManager; + } + + @Inject + public void setCacheManager(CacheManager cacheManager) { + this.cacheManager = cacheManager; + } + @Inject public void setConnectionManager(ConnectionManager connectionManager) { this.connectionManager = connectionManager; @@ -125,7 +137,7 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder { protected TimeZone getTimeZone(long deviceId, String defaultTimeZone) { TimeZone result = TimeZone.getTimeZone(defaultTimeZone); - String timeZoneName = identityManager.lookupAttributeString(deviceId, "decoder.timezone", null, false, true); + String timeZoneName = AttributeUtil.lookup(cacheManager, Keys.DECODER_TIMEZONE, deviceId); if (timeZoneName != null) { result = TimeZone.getTimeZone(timeZoneName); } diff --git a/src/main/java/org/traccar/config/ConfigKey.java b/src/main/java/org/traccar/config/ConfigKey.java index c046a46a5..b8151392c 100644 --- a/src/main/java/org/traccar/config/ConfigKey.java +++ b/src/main/java/org/traccar/config/ConfigKey.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,30 +15,34 @@ */ package org.traccar.config; +import java.util.HashSet; import java.util.List; +import java.util.Set; -public class ConfigKey<T> { +public abstract class ConfigKey<T> { private final String key; - private final List<KeyType> types; + private final Set<KeyType> types = new HashSet<>(); + private final Class<T> valueClass; private final T defaultValue; - ConfigKey(String key, List<KeyType> types) { - this(key, types, null); - } - - ConfigKey(String key, List<KeyType> types, T defaultValue) { + ConfigKey(String key, List<KeyType> types, Class<T> valueClass, T defaultValue) { this.key = key; - this.types = types; + this.types.addAll(types); + this.valueClass = valueClass; this.defaultValue = defaultValue; } - String getKey() { + public String getKey() { return key; } - public List<KeyType> getTypes() { - return types; + public boolean hasType(KeyType type) { + return types.contains(type); + } + + public Class<T> getValueClass() { + return valueClass; } public T getDefaultValue() { @@ -46,3 +50,48 @@ public class ConfigKey<T> { } } + +class StringConfigKey extends ConfigKey<String> { + StringConfigKey(String key, List<KeyType> types) { + super(key, types, String.class, null); + } + StringConfigKey(String key, List<KeyType> types, String defaultValue) { + super(key, types, String.class, defaultValue); + } +} + +class BooleanConfigKey extends ConfigKey<Boolean> { + BooleanConfigKey(String key, List<KeyType> types) { + super(key, types, Boolean.class, null); + } + BooleanConfigKey(String key, List<KeyType> types, Boolean defaultValue) { + super(key, types, Boolean.class, defaultValue); + } +} + +class IntegerConfigKey extends ConfigKey<Integer> { + IntegerConfigKey(String key, List<KeyType> types) { + super(key, types, Integer.class, null); + } + IntegerConfigKey(String key, List<KeyType> types, Integer defaultValue) { + super(key, types, Integer.class, defaultValue); + } +} + +class LongConfigKey extends ConfigKey<Long> { + LongConfigKey(String key, List<KeyType> types) { + super(key, types, Long.class, null); + } + LongConfigKey(String key, List<KeyType> types, Long defaultValue) { + super(key, types, Long.class, defaultValue); + } +} + +class DoubleConfigKey extends ConfigKey<Double> { + DoubleConfigKey(String key, List<KeyType> types) { + super(key, types, Double.class, null); + } + DoubleConfigKey(String key, List<KeyType> types, Double defaultValue) { + super(key, types, Double.class, defaultValue); + } +} diff --git a/src/main/java/org/traccar/config/ConfigSuffix.java b/src/main/java/org/traccar/config/ConfigSuffix.java index ede4c107d..aac3219c6 100644 --- a/src/main/java/org/traccar/config/ConfigSuffix.java +++ b/src/main/java/org/traccar/config/ConfigSuffix.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,15 +17,11 @@ package org.traccar.config; import java.util.List; -public class ConfigSuffix<T> { +public abstract class ConfigSuffix<T> { - private final String keySuffix; - private final List<KeyType> types; - private final T defaultValue; - - ConfigSuffix(String keySuffix, List<KeyType> types) { - this(keySuffix, types, null); - } + protected final String keySuffix; + protected final List<KeyType> types; + protected final T defaultValue; ConfigSuffix(String keySuffix, List<KeyType> types, T defaultValue) { this.keySuffix = keySuffix; @@ -33,8 +29,71 @@ public class ConfigSuffix<T> { this.defaultValue = defaultValue; } - public ConfigKey<T> withPrefix(String prefix) { - return new ConfigKey<>(prefix + keySuffix, types, defaultValue); + public abstract ConfigKey<T> withPrefix(String prefix); + +} + +class StringConfigSuffix extends ConfigSuffix<String> { + StringConfigSuffix(String key, List<KeyType> types) { + super(key, types, null); + } + StringConfigSuffix(String key, List<KeyType> types, String defaultValue) { + super(key, types, defaultValue); + } + @Override + public ConfigKey<String> withPrefix(String prefix) { + return new StringConfigKey(prefix + keySuffix, types, defaultValue); + } +} + +class BooleanConfigSuffix extends ConfigSuffix<Boolean> { + BooleanConfigSuffix(String key, List<KeyType> types) { + super(key, types, null); + } + BooleanConfigSuffix(String key, List<KeyType> types, Boolean defaultValue) { + super(key, types, defaultValue); + } + @Override + public ConfigKey<Boolean> withPrefix(String prefix) { + return new BooleanConfigKey(prefix + keySuffix, types, defaultValue); + } +} + +class IntegerConfigSuffix extends ConfigSuffix<Integer> { + IntegerConfigSuffix(String key, List<KeyType> types) { + super(key, types, null); + } + IntegerConfigSuffix(String key, List<KeyType> types, Integer defaultValue) { + super(key, types, defaultValue); + } + @Override + public ConfigKey<Integer> withPrefix(String prefix) { + return new IntegerConfigKey(prefix + keySuffix, types, defaultValue); } +} +class LongConfigSuffix extends ConfigSuffix<Long> { + LongConfigSuffix(String key, List<KeyType> types) { + super(key, types, null); + } + LongConfigSuffix(String key, List<KeyType> types, Long defaultValue) { + super(key, types, defaultValue); + } + @Override + public ConfigKey<Long> withPrefix(String prefix) { + return new LongConfigKey(prefix + keySuffix, types, defaultValue); + } +} + +class DoubleConfigSuffix extends ConfigSuffix<Double> { + DoubleConfigSuffix(String key, List<KeyType> types) { + super(key, types, null); + } + DoubleConfigSuffix(String key, List<KeyType> types, Double defaultValue) { + super(key, types, defaultValue); + } + @Override + public ConfigKey<Double> withPrefix(String prefix) { + return new DoubleConfigKey(prefix + keySuffix, types, defaultValue); + } } diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java index 292202de0..465751d38 100644 --- a/src/main/java/org/traccar/config/Keys.java +++ b/src/main/java/org/traccar/config/Keys.java @@ -15,7 +15,7 @@ */ package org.traccar.config; -import java.util.Collections; +import java.util.List; public final class Keys { @@ -25,39 +25,39 @@ public final class Keys { /** * Network interface for a the protocol. If not specified, server will bind all interfaces. */ - public static final ConfigSuffix<String> PROTOCOL_ADDRESS = new ConfigSuffix<>( + public static final ConfigSuffix<String> PROTOCOL_ADDRESS = new StringConfigSuffix( ".address", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Port number for the protocol. Most protocols use TCP on the transport layer. Some protocols use UDP. Some * support both TCP and UDP. */ - public static final ConfigSuffix<Integer> PROTOCOL_PORT = new ConfigSuffix<>( + public static final ConfigSuffix<Integer> PROTOCOL_PORT = new IntegerConfigSuffix( ".port", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * List of devices for polling protocols. List should contain unique ids separated by commas. Used only for polling * protocols. */ - public static final ConfigSuffix<String> PROTOCOL_DEVICES = new ConfigSuffix<>( + public static final ConfigSuffix<String> PROTOCOL_DEVICES = new StringConfigSuffix( ".devices", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Polling interval in seconds. Used only for polling protocols. */ - public static final ConfigSuffix<Long> PROTOCOL_INTERVAL = new ConfigSuffix<>( + public static final ConfigSuffix<Long> PROTOCOL_INTERVAL = new LongConfigSuffix( ".interval", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Enable SSL support for the protocol. Not all protocols support this. */ - public static final ConfigSuffix<Boolean> PROTOCOL_SSL = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_SSL = new BooleanConfigSuffix( ".ssl", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Connection timeout value in seconds. Because sometimes there is no way to detect lost TCP connection old @@ -65,568 +65,640 @@ public final class Keys { * problems with establishing new connections when number of devices is high or devices data connections are * unstable. */ - public static final ConfigSuffix<Integer> PROTOCOL_TIMEOUT = new ConfigSuffix<>( + public static final ConfigSuffix<Integer> PROTOCOL_TIMEOUT = new IntegerConfigSuffix( ".timeout", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Device password. Commonly used in some protocol for sending commands. */ - public static final ConfigSuffix<String> PROTOCOL_DEVICE_PASSWORD = new ConfigSuffix<>( + public static final ConfigSuffix<String> PROTOCOL_DEVICE_PASSWORD = new StringConfigSuffix( ".devicePassword", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Default protocol mask to use. Currently used only by Skypatrol protocol. */ - public static final ConfigSuffix<Integer> PROTOCOL_MASK = new ConfigSuffix<>( + public static final ConfigSuffix<Integer> PROTOCOL_MASK = new IntegerConfigSuffix( ".mask", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Custom message length. Currently used only by H2 protocol for specifying binary message length. */ - public static final ConfigSuffix<Integer> PROTOCOL_MESSAGE_LENGTH = new ConfigSuffix<>( + public static final ConfigSuffix<Integer> PROTOCOL_MESSAGE_LENGTH = new IntegerConfigSuffix( ".messageLength", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Enable extended functionality for the protocol. The reason it's disabled by default is that not all devices * support it. */ - public static final ConfigSuffix<Boolean> PROTOCOL_EXTENDED = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_EXTENDED = new BooleanConfigSuffix( ".extended", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Decode string as UTF8 instead of ASCII. Only applicable for some protocols. */ - public static final ConfigSuffix<Boolean> PROTOCOL_UTF8 = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_UTF8 = new BooleanConfigSuffix( ".utf8", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Enable CAN decoding for the protocol. Similar to 'extended' configuration, it's not supported for some devices. */ - public static final ConfigSuffix<Boolean> PROTOCOL_CAN = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_CAN = new BooleanConfigSuffix( ".can", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Indicates whether server acknowledgement is required. Only applicable for some protocols. */ - public static final ConfigSuffix<Boolean> PROTOCOL_ACK = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_ACK = new BooleanConfigSuffix( ".ack", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Ignore device reported fix time. Useful in case some devices report invalid time. Currently only available for * GL200 protocol. */ - public static final ConfigSuffix<Boolean> PROTOCOL_IGNORE_FIX_TIME = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_IGNORE_FIX_TIME = new BooleanConfigSuffix( ".ignoreFixTime", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Decode additional TK103 attributes. Not supported for some devices. */ - public static final ConfigSuffix<Boolean> PROTOCOL_DECODE_LOW = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_DECODE_LOW = new BooleanConfigSuffix( ".decodeLow", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Use long date format for Atrack protocol. */ - public static final ConfigSuffix<Boolean> PROTOCOL_LONG_DATE = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_LONG_DATE = new BooleanConfigSuffix( ".longDate", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Use decimal fuel value format for Atrack protocol. */ - public static final ConfigSuffix<Boolean> PROTOCOL_DECIMAL_FUEL = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_DECIMAL_FUEL = new BooleanConfigSuffix( ".decimalFuel", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Indicates additional custom attributes for Atrack protocol. */ - public static final ConfigSuffix<Boolean> PROTOCOL_CUSTOM = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_CUSTOM = new BooleanConfigSuffix( ".custom", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Custom format string for Atrack protocol. */ - public static final ConfigSuffix<String> PROTOCOL_FORM = new ConfigSuffix<>( + public static final ConfigSuffix<String> PROTOCOL_FORM = new StringConfigSuffix( ".form", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Protocol configuration. Required for some devices for decoding incoming data. */ - public static final ConfigSuffix<String> PROTOCOL_CONFIG = new ConfigSuffix<>( + public static final ConfigSuffix<String> PROTOCOL_CONFIG = new StringConfigSuffix( ".config", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Alarm mapping for Atrack protocol. */ - public static final ConfigSuffix<String> PROTOCOL_ALARM_MAP = new ConfigSuffix<>( + public static final ConfigSuffix<String> PROTOCOL_ALARM_MAP = new StringConfigSuffix( ".alarmMap", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Indicates whether TAIP protocol should have prefixes for messages. */ - public static final ConfigSuffix<Boolean> PROTOCOL_PREFIX = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_PREFIX = new BooleanConfigSuffix( ".prefix", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Some devices require server address confirmation. Use this parameter to configure correct public address. */ - public static final ConfigSuffix<String> PROTOCOL_SERVER = new ConfigSuffix<>( + public static final ConfigSuffix<String> PROTOCOL_SERVER = new StringConfigSuffix( ".server", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); + + /** + * Protocol type for Suntech. + */ + public static final ConfigKey<Integer> PROTOCOL_TYPE = new IntegerConfigKey( + "suntech.protocolType", + List.of(KeyType.CONFIG, KeyType.DEVICE)); + + /** + * Suntech HBM configuration value. + */ + public static final ConfigKey<Boolean> PROTOCOL_HBM = new BooleanConfigKey( + "suntech.hbm", + List.of(KeyType.CONFIG, KeyType.DEVICE)); + + /** + * Format includes ADC value. + */ + public static final ConfigSuffix<Boolean> PROTOCOL_INCLUDE_ADC = new BooleanConfigSuffix( + ".includeAdc", + List.of(KeyType.CONFIG, KeyType.DEVICE)); + + /** + * Format includes RPM value. + */ + public static final ConfigSuffix<Boolean> PROTOCOL_INCLUDE_RPM = new BooleanConfigSuffix( + ".includeRpm", + List.of(KeyType.CONFIG, KeyType.DEVICE)); + + /** + * Format includes temperature values. + */ + public static final ConfigSuffix<Boolean> PROTOCOL_INCLUDE_TEMPERATURE = new BooleanConfigSuffix( + ".includeTemp", + List.of(KeyType.CONFIG, KeyType.DEVICE)); + + /** + * Protocol format. Used by protocols that have configurable message format. + */ + public static final ConfigSuffix<String> PROTOCOL_FORMAT = new StringConfigSuffix( + ".format", + List.of(KeyType.DEVICE)); + + /** + * Protocol date format. Used by protocols that have configurable date format. + */ + public static final ConfigSuffix<String> PROTOCOL_DATE_FORMAT = new StringConfigSuffix( + ".dateFormat", + List.of(KeyType.DEVICE)); + + /** + * Device time zone. Most devices report UTC time, but in some cases devices report local time, so this parameter + * needs to be configured for the server to be able to decode the time correctly. + */ + public static final ConfigKey<String> DECODER_TIMEZONE = new StringConfigKey( + "decoder.timezone", + List.of(KeyType.CONFIG, KeyType.DEVICE)); /** * ORBCOMM API access id. */ - public static final ConfigKey<String> ORBCOMM_ACCESS_ID = new ConfigKey<>( + public static final ConfigKey<String> ORBCOMM_ACCESS_ID = new StringConfigKey( "orbcomm.accessId", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * ORBCOMM API password. */ - public static final ConfigKey<String> ORBCOMM_PASSWORD = new ConfigKey<>( + public static final ConfigKey<String> ORBCOMM_PASSWORD = new StringConfigKey( "orbcomm.password", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Server wide connection timeout value in seconds. See protocol timeout for more information. */ - public static final ConfigKey<Integer> SERVER_TIMEOUT = new ConfigKey<>( + public static final ConfigKey<Integer> SERVER_TIMEOUT = new IntegerConfigKey( "server.timeout", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Address for uploading aggregated anonymous usage statistics. Uploaded information is the same you can see on the * statistics screen in the web app. It does not include any sensitive (e.g. locations). */ - public static final ConfigKey<String> SERVER_STATISTICS = new ConfigKey<>( + public static final ConfigKey<String> SERVER_STATISTICS = new StringConfigKey( "server.statistics", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); + + /** + * Fuel drop threshold value. When fuel level drops from one position to another for more the value, an event is + * generated. + */ + public static final ConfigKey<Double> EVENT_FUEL_DROP_THRESHOLD = new DoubleConfigKey( + "fuelDropThreshold", + List.of(KeyType.SERVER, KeyType.DEVICE)); + + /** + * Speed limit value in knots. + */ + public static final ConfigKey<Double> EVENT_OVERSPEED_LIMIT = new DoubleConfigKey( + "speedLimit", + List.of(KeyType.SERVER, KeyType.DEVICE)); /** * If true, the event is generated once at the beginning of overspeeding period. */ - public static final ConfigKey<Boolean> EVENT_OVERSPEED_NOT_REPEAT = new ConfigKey<>( + public static final ConfigKey<Boolean> EVENT_OVERSPEED_NOT_REPEAT = new BooleanConfigKey( "event.overspeed.notRepeat", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Minimal over speed duration to trigger the event. Value in seconds. */ - public static final ConfigKey<Long> EVENT_OVERSPEED_MINIMAL_DURATION = new ConfigKey<>( + public static final ConfigKey<Long> EVENT_OVERSPEED_MINIMAL_DURATION = new LongConfigKey( "event.overspeed.minimalDuration", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Relevant only for geofence speed limits. Use the lowest speed limit from all geofences. */ - public static final ConfigKey<Boolean> EVENT_OVERSPEED_PREFER_LOWEST = new ConfigKey<>( + public static final ConfigKey<Boolean> EVENT_OVERSPEED_PREFER_LOWEST = new BooleanConfigKey( "event.overspeed.preferLowest", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Driver behavior acceleration threshold. Value is in meter per second squared. */ - public static final ConfigKey<Double> EVENT_BEHAVIOR_ACCELERATION_THRESHOLD = new ConfigKey<>( + public static final ConfigKey<Double> EVENT_BEHAVIOR_ACCELERATION_THRESHOLD = new DoubleConfigKey( "event.behavior.accelerationThreshold", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Driver behavior braking threshold. Value is in meter per second squared. */ - public static final ConfigKey<Double> EVENT_BEHAVIOR_BRAKING_THRESHOLD = new ConfigKey<>( + public static final ConfigKey<Double> EVENT_BEHAVIOR_BRAKING_THRESHOLD = new DoubleConfigKey( "event.behavior.brakingThreshold", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Do not generate alert event if same alert was present in last known location. */ - public static final ConfigKey<Boolean> EVENT_IGNORE_DUPLICATE_ALERTS = new ConfigKey<>( + public static final ConfigKey<Boolean> EVENT_IGNORE_DUPLICATE_ALERTS = new BooleanConfigKey( "event.ignoreDuplicateAlerts", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * If set to true, invalid positions will be considered for motion logic. */ - public static final ConfigKey<Boolean> EVENT_MOTION_PROCESS_INVALID_POSITIONS = new ConfigKey<>( + public static final ConfigKey<Boolean> EVENT_MOTION_PROCESS_INVALID_POSITIONS = new BooleanConfigKey( "event.motion.processInvalidPositions", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * If the speed is above specified value, the object is considered to be in motion. Default value is 0.01 knots. */ - public static final ConfigKey<Double> EVENT_MOTION_SPEED_THRESHOLD = new ConfigKey<>( + public static final ConfigKey<Double> EVENT_MOTION_SPEED_THRESHOLD = new DoubleConfigKey( "event.motion.speedThreshold", - Collections.singletonList(KeyType.CONFIG), + List.of(KeyType.CONFIG), 0.01); /** * Global polyline geofence distance. Within that distance from the polyline, point is considered within the * geofence. Each individual geofence can also has 'polylineDistance' attribute which will take precedence. */ - public static final ConfigKey<Double> GEOFENCE_POLYLINE_DISTANCE = new ConfigKey<>( + public static final ConfigKey<Double> GEOFENCE_POLYLINE_DISTANCE = new DoubleConfigKey( "geofence.polylineDistance", - Collections.singletonList(KeyType.CONFIG), + List.of(KeyType.CONFIG), 25.0); /** * Path to the database driver JAR file. Traccar includes drivers for MySQL, PostgreSQL and H2 databases. If you use * one of those, you don't need to specify this parameter. */ - public static final ConfigKey<String> DATABASE_DRIVER_FILE = new ConfigKey<>( + public static final ConfigKey<String> DATABASE_DRIVER_FILE = new StringConfigKey( "database.driverFile", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Database driver Java class. For H2 use 'org.h2.Driver'. MySQL driver class name is 'com.mysql.jdbc.Driver'. */ - public static final ConfigKey<String> DATABASE_DRIVER = new ConfigKey<>( + public static final ConfigKey<String> DATABASE_DRIVER = new StringConfigKey( "database.driver", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Database connection URL. By default Traccar uses H2 database. */ - public static final ConfigKey<String> DATABASE_URL = new ConfigKey<>( + public static final ConfigKey<String> DATABASE_URL = new StringConfigKey( "database.url", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Database user name. Default administrator user for H2 database is 'sa'. */ - public static final ConfigKey<String> DATABASE_USER = new ConfigKey<>( + public static final ConfigKey<String> DATABASE_USER = new StringConfigKey( "database.user", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Database user password. Default password for H2 admin (sa) user is empty. */ - public static final ConfigKey<String> DATABASE_PASSWORD = new ConfigKey<>( + public static final ConfigKey<String> DATABASE_PASSWORD = new StringConfigKey( "database.password", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Path to Liquibase master changelog file. */ - public static final ConfigKey<String> DATABASE_CHANGELOG = new ConfigKey<>( + public static final ConfigKey<String> DATABASE_CHANGELOG = new StringConfigKey( "database.changelog", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Database connection pool size. Default value is defined by the HikariCP library. */ - public static final ConfigKey<Integer> DATABASE_MAX_POOL_SIZE = new ConfigKey<>( + public static final ConfigKey<Integer> DATABASE_MAX_POOL_SIZE = new IntegerConfigKey( "database.maxPoolSize", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * SQL query to check connection status. Default value is 'SELECT 1'. For Oracle database you can use * 'SELECT 1 FROM DUAL'. */ - public static final ConfigKey<String> DATABASE_CHECK_CONNECTION = new ConfigKey<>( + public static final ConfigKey<String> DATABASE_CHECK_CONNECTION = new StringConfigKey( "database.checkConnection", - Collections.singletonList(KeyType.CONFIG), + List.of(KeyType.CONFIG), "SELECT 1"); /** * Store original HEX or string data as "raw" attribute in the corresponding position. */ - public static final ConfigKey<Boolean> DATABASE_SAVE_ORIGINAL = new ConfigKey<>( + public static final ConfigKey<Boolean> DATABASE_SAVE_ORIGINAL = new BooleanConfigKey( "database.saveOriginal", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * By default server syncs with the database if it encounters and unknown device. This flag allows to disable that * behavior to improve performance in some cases. */ - public static final ConfigKey<Boolean> DATABASE_IGNORE_UNKNOWN = new ConfigKey<>( + public static final ConfigKey<Boolean> DATABASE_IGNORE_UNKNOWN = new BooleanConfigKey( "database.ignoreUnknown", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Automatically register unknown devices in the database. */ - public static final ConfigKey<Boolean> DATABASE_REGISTER_UNKNOWN = new ConfigKey<>( + public static final ConfigKey<Boolean> DATABASE_REGISTER_UNKNOWN = new BooleanConfigKey( "database.registerUnknown", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Default category for auto-registered devices. */ - public static final ConfigKey<String> DATABASE_REGISTER_UNKNOWN_DEFAULT_CATEGORY = new ConfigKey<>( + public static final ConfigKey<String> DATABASE_REGISTER_UNKNOWN_DEFAULT_CATEGORY = new StringConfigKey( "database.registerUnknown.defaultCategory", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * The group id assigned to auto-registered devices. */ - public static final ConfigKey<Long> DATABASE_REGISTER_UNKNOWN_DEFAULT_GROUP_ID = new ConfigKey<>( + public static final ConfigKey<Long> DATABASE_REGISTER_UNKNOWN_DEFAULT_GROUP_ID = new LongConfigKey( "database.registerUnknown.defaultGroupId", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Minimum device refresh timeout in seconds. Default timeout is 5 minutes. */ - public static final ConfigKey<Long> DATABASE_REFRESH_DELAY = new ConfigKey<>( + public static final ConfigKey<Long> DATABASE_REFRESH_DELAY = new LongConfigKey( "database.refreshDelay", - Collections.singletonList(KeyType.CONFIG), + List.of(KeyType.CONFIG), 300L); /** * Store empty messages as positions. For example, heartbeats. */ - public static final ConfigKey<Boolean> DATABASE_SAVE_EMPTY = new ConfigKey<>( + public static final ConfigKey<Boolean> DATABASE_SAVE_EMPTY = new BooleanConfigKey( "database.saveEmpty", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Device limit for self registered users. Default value is -1, which indicates no limit. */ - public static final ConfigKey<Integer> USERS_DEFAULT_DEVICE_LIMIT = new ConfigKey<>( + public static final ConfigKey<Integer> USERS_DEFAULT_DEVICE_LIMIT = new IntegerConfigKey( "users.defaultDeviceLimit", - Collections.singletonList(KeyType.CONFIG), + List.of(KeyType.CONFIG), -1); /** * Default user expiration for self registered users. Value is in days. By default no expiration is set. */ - public static final ConfigKey<Integer> USERS_DEFAULT_EXPIRATION_DAYS = new ConfigKey<>( + public static final ConfigKey<Integer> USERS_DEFAULT_EXPIRATION_DAYS = new IntegerConfigKey( "users.defaultExpirationDays", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * LDAP server URL. */ - public static final ConfigKey<String> LDAP_URL = new ConfigKey<>( + public static final ConfigKey<String> LDAP_URL = new StringConfigKey( "ldap.url", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * LDAP server login. */ - public static final ConfigKey<String> LDAP_USER = new ConfigKey<>( + public static final ConfigKey<String> LDAP_USER = new StringConfigKey( "ldap.user", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * LDAP server password. */ - public static final ConfigKey<String> LDAP_PASSWORD = new ConfigKey<>( + public static final ConfigKey<String> LDAP_PASSWORD = new StringConfigKey( "ldap.password", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Force LDAP authentication. */ - public static final ConfigKey<Boolean> LDAP_FORCE = new ConfigKey<>( + public static final ConfigKey<Boolean> LDAP_FORCE = new BooleanConfigKey( "ldap.force", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * LDAP user search base. */ - public static final ConfigKey<String> LDAP_BASE = new ConfigKey<>( + public static final ConfigKey<String> LDAP_BASE = new StringConfigKey( "ldap.base", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * LDAP attribute used as user id. Default value is 'uid'. */ - public static final ConfigKey<String> LDAP_ID_ATTRIBUTE = new ConfigKey<>( + public static final ConfigKey<String> LDAP_ID_ATTRIBUTE = new StringConfigKey( "ldap.idAttribute", - Collections.singletonList(KeyType.CONFIG), + List.of(KeyType.CONFIG), "uid"); /** * LDAP attribute used as user name. Default value is 'cn'. */ - public static final ConfigKey<String> LDAP_NAME_ATTRIBUTE = new ConfigKey<>( + public static final ConfigKey<String> LDAP_NAME_ATTRIBUTE = new StringConfigKey( "ldap.nameAttribute", - Collections.singletonList(KeyType.CONFIG), + List.of(KeyType.CONFIG), "cn"); /** * LDAP attribute used as user email. Default value is 'mail'. */ - public static final ConfigKey<String> LDAP_MAIN_ATTRIBUTE = new ConfigKey<>( + public static final ConfigKey<String> LDAP_MAIN_ATTRIBUTE = new StringConfigKey( "ldap.mailAttribute", - Collections.singletonList(KeyType.CONFIG), + List.of(KeyType.CONFIG), "mail"); /** * LDAP custom search filter. If not specified, '({idAttribute}=:login)' will be used as a filter. */ - public static final ConfigKey<String> LDAP_SEARCH_FILTER = new ConfigKey<>( + public static final ConfigKey<String> LDAP_SEARCH_FILTER = new StringConfigKey( "ldap.searchFilter", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * LDAP custom admin search filter. */ - public static final ConfigKey<String> LDAP_ADMIN_FILTER = new ConfigKey<>( + public static final ConfigKey<String> LDAP_ADMIN_FILTER = new StringConfigKey( "ldap.adminFilter", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * LDAP admin user group. Used if custom admin filter is not specified. */ - public static final ConfigKey<String> LDAP_ADMIN_GROUP = new ConfigKey<>( + public static final ConfigKey<String> LDAP_ADMIN_GROUP = new StringConfigKey( "ldap.adminGroup", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * If no data is reported by a device for the given amount of time, status changes from online to unknown. Value is * in seconds. Default timeout is 10 minutes. */ - public static final ConfigKey<Long> STATUS_TIMEOUT = new ConfigKey<>( + public static final ConfigKey<Long> STATUS_TIMEOUT = new LongConfigKey( "status.timeout", - Collections.singletonList(KeyType.CONFIG), + List.of(KeyType.CONFIG), 600L); /** * Force additional state check when device status changes to 'offline' or 'unknown'. Default false. */ - public static final ConfigKey<Boolean> STATUS_UPDATE_DEVICE_STATE = new ConfigKey<>( + public static final ConfigKey<Boolean> STATUS_UPDATE_DEVICE_STATE = new BooleanConfigKey( "status.updateDeviceState", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * List of protocol names to ignore offline status. Can be useful to not trigger status change when devices are * configured to disconnect after reporting a batch of data. */ - public static final ConfigKey<String> STATUS_IGNORE_OFFLINE = new ConfigKey<>( + public static final ConfigKey<String> STATUS_IGNORE_OFFLINE = new StringConfigKey( "status.ignoreOffline", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Path to the media folder. Server stores audio, video and photo files in that folder. Sub-folders will be * automatically created for each device by unique id. */ - public static final ConfigKey<String> MEDIA_PATH = new ConfigKey<>( + public static final ConfigKey<String> MEDIA_PATH = new StringConfigKey( "media.path", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Optional parameter to specify network interface for web interface to bind to. By default server will bind to all * available interfaces. */ - public static final ConfigKey<String> WEB_ADDRESS = new ConfigKey<>( + public static final ConfigKey<String> WEB_ADDRESS = new StringConfigKey( "web.address", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Web interface TCP port number. By default Traccar uses port 8082. To avoid specifying port in the browser you * can set it to 80 (default HTTP port). */ - public static final ConfigKey<Integer> WEB_PORT = new ConfigKey<>( + public static final ConfigKey<Integer> WEB_PORT = new IntegerConfigKey( "web.port", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Sanitize all strings returned via API. This is needed to fix XSS issues in the old web interface. New React-based * interface doesn't require this. */ - public static final ConfigKey<Boolean> WEB_SANITIZE = new ConfigKey<>( + public static final ConfigKey<Boolean> WEB_SANITIZE = new BooleanConfigKey( "web.sanitize", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Path to the web app folder. */ - public static final ConfigKey<String> WEB_PATH = new ConfigKey<>( + public static final ConfigKey<String> WEB_PATH = new StringConfigKey( "web.path", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * WebSocket connection timeout in milliseconds. Default timeout is 10 minutes. */ - public static final ConfigKey<Long> WEB_TIMEOUT = new ConfigKey<>( + public static final ConfigKey<Long> WEB_TIMEOUT = new LongConfigKey( "web.timeout", - Collections.singletonList(KeyType.CONFIG), + List.of(KeyType.CONFIG), 60000L); /** * Authentication sessions timeout in seconds. By default no timeout. */ - public static final ConfigKey<Integer> WEB_SESSION_TIMEOUT = new ConfigKey<>( + public static final ConfigKey<Integer> WEB_SESSION_TIMEOUT = new IntegerConfigKey( "web.sessionTimeout", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Enable database access console via '/console' URL. Use only for debugging. Never use in production. */ - public static final ConfigKey<Boolean> WEB_CONSOLE = new ConfigKey<>( + public static final ConfigKey<Boolean> WEB_CONSOLE = new BooleanConfigKey( "web.console", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Server debug version of the web app. Not recommended to use for performance reasons. It is intended to be used * for development and debugging purposes. */ - public static final ConfigKey<Boolean> WEB_DEBUG = new ConfigKey<>( + public static final ConfigKey<Boolean> WEB_DEBUG = new BooleanConfigKey( "web.debug", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Cross-origin resource sharing origin header value. */ - public static final ConfigKey<String> WEB_ORIGIN = new ConfigKey<>( + public static final ConfigKey<String> WEB_ORIGIN = new StringConfigKey( "web.origin", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Cache control header value. By default resources are cached for one hour. */ - public static final ConfigKey<String> WEB_CACHE_CONTROL = new ConfigKey<>( + public static final ConfigKey<String> WEB_CACHE_CONTROL = new StringConfigKey( "web.cacheControl", - Collections.singletonList(KeyType.CONFIG), + List.of(KeyType.CONFIG), "max-age=3600,public"); /** * URL to forward positions. Data is passed through URL parameters. For example, {uniqueId} for device identifier, * {latitude} and {longitude} for coordinates. */ - public static final ConfigKey<String> FORWARD_URL = new ConfigKey<>( + public static final ConfigKey<String> FORWARD_URL = new StringConfigKey( "forward.url", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Additional HTTP header, can be used for authorization. */ - public static final ConfigKey<String> FORWARD_HEADER = new ConfigKey<>( + public static final ConfigKey<String> FORWARD_HEADER = new StringConfigKey( "forward.header", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Boolean value to enable forwarding in JSON format. */ - public static final ConfigKey<Boolean> FORWARD_JSON = new ConfigKey<>( + public static final ConfigKey<Boolean> FORWARD_JSON = new BooleanConfigKey( "forward.json", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Boolean value to enable URL parameters in json mode. For example, {uniqueId} for device identifier, * {latitude} and {longitude} for coordinates. */ - public static final ConfigKey<Boolean> FORWARD_URL_VARIABLES = new ConfigKey<>( + public static final ConfigKey<Boolean> FORWARD_URL_VARIABLES = new BooleanConfigKey( "forward.urlVariables", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Position forwarding retrying enable. When enabled, additional attempts are made to deliver positions. If initial @@ -635,301 +707,301 @@ public final class Keys { * If forwarding is retried for 'forward.retry.count', retrying is canceled and the position is dropped. Positions * pending to be delivered are limited to 'forward.retry.limit'. If this limit is reached, positions get discarded. */ - public static final ConfigKey<Boolean> FORWARD_RETRY_ENABLE = new ConfigKey<>( + public static final ConfigKey<Boolean> FORWARD_RETRY_ENABLE = new BooleanConfigKey( "forward.retry.enable", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Position forwarding retry first delay in milliseconds. * Can be set to anything greater than 0. Defaults to 100 milliseconds. */ - public static final ConfigKey<Integer> FORWARD_RETRY_DELAY = new ConfigKey<>( + public static final ConfigKey<Integer> FORWARD_RETRY_DELAY = new IntegerConfigKey( "forward.retry.delay", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Position forwarding retry maximum retries. * Can be set to anything greater than 0. Defaults to 10 retries. */ - public static final ConfigKey<Integer> FORWARD_RETRY_COUNT = new ConfigKey<>( + public static final ConfigKey<Integer> FORWARD_RETRY_COUNT = new IntegerConfigKey( "forward.retry.count", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Position forwarding retry pending positions limit. * Can be set to anything greater than 0. Defaults to 100 positions. */ - public static final ConfigKey<Integer> FORWARD_RETRY_LIMIT = new ConfigKey<>( + public static final ConfigKey<Integer> FORWARD_RETRY_LIMIT = new IntegerConfigKey( "forward.retry.limit", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Events forwarding URL. */ - public static final ConfigKey<String> EVENT_FORWARD_URL = new ConfigKey<>( + public static final ConfigKey<String> EVENT_FORWARD_URL = new StringConfigKey( "event.forward.url", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Events forwarding headers. Example value: * FirstHeader: hello * SecondHeader: world */ - public static final ConfigKey<String> EVENT_FORWARD_HEADERS = new ConfigKey<>( + public static final ConfigKey<String> EVENT_FORWARD_HEADERS = new StringConfigKey( "event.forward.header", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Enable commands queuing when devices are offline. Commands are buffered in memory only, so restarting service * will clear the buffer. */ - public static final ConfigKey<Boolean> COMMANDS_QUEUEING = new ConfigKey<>( + public static final ConfigKey<Boolean> COMMANDS_QUEUEING = new BooleanConfigKey( "commands.queueing", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Root folder for all template files. */ - public static final ConfigKey<String> TEMPLATES_ROOT = new ConfigKey<>( + public static final ConfigKey<String> TEMPLATES_ROOT = new StringConfigKey( "templates.root", - Collections.singletonList(KeyType.CONFIG), + List.of(KeyType.CONFIG), "templates"); /** * SMS API service full URL. Enables SMS commands and notifications. */ - public static final ConfigKey<String> SMS_HTTP_URL = new ConfigKey<>( + public static final ConfigKey<String> SMS_HTTP_URL = new StringConfigKey( "sms.http.url", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * SMS API authorization header name. Default value is 'Authorization'. */ - public static final ConfigKey<String> SMS_HTTP_AUTHORIZATION_HEADER = new ConfigKey<>( + public static final ConfigKey<String> SMS_HTTP_AUTHORIZATION_HEADER = new StringConfigKey( "sms.http.authorizationHeader", - Collections.singletonList(KeyType.CONFIG), + List.of(KeyType.CONFIG), "Authorization"); /** * SMS API authorization header value. This value takes precedence over user and password. */ - public static final ConfigKey<String> SMS_HTTP_AUTHORIZATION = new ConfigKey<>( + public static final ConfigKey<String> SMS_HTTP_AUTHORIZATION = new StringConfigKey( "sms.http.authorization", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * SMS API basic authentication user. */ - public static final ConfigKey<String> SMS_HTTP_USER = new ConfigKey<>( + public static final ConfigKey<String> SMS_HTTP_USER = new StringConfigKey( "sms.http.user", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * SMS API basic authentication password. */ - public static final ConfigKey<String> SMS_HTTP_PASSWORD = new ConfigKey<>( + public static final ConfigKey<String> SMS_HTTP_PASSWORD = new StringConfigKey( "sms.http.password", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * SMS API body template. Placeholders {phone} and {message} can be used in the template. * If value starts with '{' or '[', server automatically assumes JSON format. */ - public static final ConfigKey<String> SMS_HTTP_TEMPLATE = new ConfigKey<>( + public static final ConfigKey<String> SMS_HTTP_TEMPLATE = new StringConfigKey( "sms.http.template", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * AWS Access Key with SNS permission. */ - public static final ConfigKey<String> SMS_AWS_ACCESS = new ConfigKey<>( + public static final ConfigKey<String> SMS_AWS_ACCESS = new StringConfigKey( "sms.aws.access", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * AWS Secret Access Key with SNS permission. */ - public static final ConfigKey<String> SMS_AWS_SECRET = new ConfigKey<>( + public static final ConfigKey<String> SMS_AWS_SECRET = new StringConfigKey( "sms.aws.secret", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * AWS Region for SNS service. * Make sure to use regions that are supported for messaging. */ - public static final ConfigKey<String> SMS_AWS_REGION = new ConfigKey<>( + public static final ConfigKey<String> SMS_AWS_REGION = new StringConfigKey( "sms.aws.region", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Enabled notification options. Comma-separated string is expected. * Example: web,mail,sms */ - public static final ConfigKey<String> NOTIFICATOR_TYPES = new ConfigKey<>( + public static final ConfigKey<String> NOTIFICATOR_TYPES = new StringConfigKey( "notificator.types", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Traccar notification API key. */ - public static final ConfigKey<String> NOTIFICATOR_TRACCAR_KEY = new ConfigKey<>( + public static final ConfigKey<String> NOTIFICATOR_TRACCAR_KEY = new StringConfigKey( "notificator.traccar.key", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Firebase server API key for push notifications. */ - public static final ConfigKey<String> NOTIFICATOR_FIREBASE_KEY = new ConfigKey<>( + public static final ConfigKey<String> NOTIFICATOR_FIREBASE_KEY = new StringConfigKey( "notificator.firebase.key", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Pushover notification user name. */ - public static final ConfigKey<String> NOTIFICATOR_PUSHOVER_USER = new ConfigKey<>( + public static final ConfigKey<String> NOTIFICATOR_PUSHOVER_USER = new StringConfigKey( "notificator.pushover.user", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Pushover notification user token. */ - public static final ConfigKey<String> NOTIFICATOR_PUSHOVER_TOKEN = new ConfigKey<>( + public static final ConfigKey<String> NOTIFICATOR_PUSHOVER_TOKEN = new StringConfigKey( "notificator.pushover.token", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Telegram notification API key. */ - public static final ConfigKey<String> NOTIFICATOR_TELEGRAM_KEY = new ConfigKey<>( + public static final ConfigKey<String> NOTIFICATOR_TELEGRAM_KEY = new StringConfigKey( "notificator.telegram.key", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Telegram notification chat id to post messages to. */ - public static final ConfigKey<String> NOTIFICATOR_TELEGRAM_CHAT_ID = new ConfigKey<>( + public static final ConfigKey<String> NOTIFICATOR_TELEGRAM_CHAT_ID = new StringConfigKey( "notificator.telegram.chatId", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Telegram notification send location message. */ - public static final ConfigKey<Boolean> NOTIFICATOR_TELEGRAM_SEND_LOCATION = new ConfigKey<>( + public static final ConfigKey<Boolean> NOTIFICATOR_TELEGRAM_SEND_LOCATION = new BooleanConfigKey( "notificator.telegram.sendLocation", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Maximum time period for reports in seconds. Can be useful to prevent users to request unreasonably long reports. * By default there is no limit. */ - public static final ConfigKey<Long> REPORT_PERIOD_LIMIT = new ConfigKey<>( + public static final ConfigKey<Long> REPORT_PERIOD_LIMIT = new LongConfigKey( "report.periodLimit", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Trips less than minimal duration and minimal distance are ignored. 300 seconds and 500 meters are default. */ - public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_TRIP_DISTANCE = new ConfigKey<>( + public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_TRIP_DISTANCE = new LongConfigKey( "report.trip.minimalTripDistance", - Collections.singletonList(KeyType.CONFIG), + List.of(KeyType.CONFIG), 500L); /** * Trips less than minimal duration and minimal distance are ignored. 300 seconds and 500 meters are default. */ - public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_TRIP_DURATION = new ConfigKey<>( + public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_TRIP_DURATION = new LongConfigKey( "report.trip.minimalTripDuration", - Collections.singletonList(KeyType.CONFIG), + List.of(KeyType.CONFIG), 300L); /** * Parking less than minimal duration does not cut trip. Default 300 seconds. */ - public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_PARKING_DURATION = new ConfigKey<>( + public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_PARKING_DURATION = new LongConfigKey( "report.trip.minimalParkingDuration", - Collections.singletonList(KeyType.CONFIG), + List.of(KeyType.CONFIG), 300L); /** * Gaps of more than specified time are counted as stops. Default value is one hour. */ - public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_NO_DATA_DURATION = new ConfigKey<>( + public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_NO_DATA_DURATION = new LongConfigKey( "report.trip.minimalNoDataDuration", - Collections.singletonList(KeyType.CONFIG), + List.of(KeyType.CONFIG), 3600L); /** * Flag to enable ignition use for trips calculation. */ - public static final ConfigKey<Boolean> REPORT_TRIP_USE_IGNITION = new ConfigKey<>( + public static final ConfigKey<Boolean> REPORT_TRIP_USE_IGNITION = new BooleanConfigKey( "report.trip.useIgnition", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Boolean flag to enable or disable position filtering. */ - public static final ConfigKey<Boolean> FILTER_ENABLE = new ConfigKey<>( + public static final ConfigKey<Boolean> FILTER_ENABLE = new BooleanConfigKey( "filter.enable", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Filter invalid (valid field is set to false) positions. */ - public static final ConfigKey<Boolean> FILTER_INVALID = new ConfigKey<>( + public static final ConfigKey<Boolean> FILTER_INVALID = new BooleanConfigKey( "filter.invalid", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Filter zero coordinates. Zero latitude and longitude are theoretically valid values, but it practice it usually * indicates invalid GPS data. */ - public static final ConfigKey<Boolean> FILTER_ZERO = new ConfigKey<>( + public static final ConfigKey<Boolean> FILTER_ZERO = new BooleanConfigKey( "filter.zero", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Filter duplicate records (duplicates are detected by time value). */ - public static final ConfigKey<Boolean> FILTER_DUPLICATE = new ConfigKey<>( + public static final ConfigKey<Boolean> FILTER_DUPLICATE = new BooleanConfigKey( "filter.duplicate", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Filter records with fix time in future. The values is specified in seconds. Records that have fix time more than * specified number of seconds later than current server time would be filtered out. */ - public static final ConfigKey<Long> FILTER_FUTURE = new ConfigKey<>( + public static final ConfigKey<Long> FILTER_FUTURE = new LongConfigKey( "filter.future", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Filter positions with accuracy less than specified value in meters. */ - public static final ConfigKey<Integer> FILTER_ACCURACY = new ConfigKey<>( + public static final ConfigKey<Integer> FILTER_ACCURACY = new IntegerConfigKey( "filter.accuracy", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Filter cell and wifi locations that are coming from geolocation provider. */ - public static final ConfigKey<Boolean> FILTER_APPROXIMATE = new ConfigKey<>( + public static final ConfigKey<Boolean> FILTER_APPROXIMATE = new BooleanConfigKey( "filter.approximate", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Filter positions with exactly zero speed values. */ - public static final ConfigKey<Boolean> FILTER_STATIC = new ConfigKey<>( + public static final ConfigKey<Boolean> FILTER_STATIC = new BooleanConfigKey( "filter.static", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Filter records by distance. The values is specified in meters. If the new position is less far than this value * from the last one it gets filtered out. */ - public static final ConfigKey<Integer> FILTER_DISTANCE = new ConfigKey<>( + public static final ConfigKey<Integer> FILTER_DISTANCE = new IntegerConfigKey( "filter.distance", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Filter records by Maximum Speed value in knots. Can be used to filter jumps to far locations even if Position @@ -938,16 +1010,16 @@ public final class Keys { * * Tip: Shouldn't be too low. Start testing with values at about 25000. */ - public static final ConfigKey<Integer> FILTER_MAX_SPEED = new ConfigKey<>( + public static final ConfigKey<Integer> FILTER_MAX_SPEED = new IntegerConfigKey( "filter.maxSpeed", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Filter position if time from previous position is less than specified value in seconds. */ - public static final ConfigKey<Integer> FILTER_MIN_PERIOD = new ConfigKey<>( + public static final ConfigKey<Integer> FILTER_MIN_PERIOD = new IntegerConfigKey( "filter.minPeriod", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * If false, the server expects all locations to come sequentially (for each device). Filter checks for duplicates, @@ -957,262 +1029,271 @@ public final class Keys { * Filter checks for duplicates, distance, speed, or time period against the preceding Position's. * Important: setting to true can cause potential performance issues. */ - public static final ConfigKey<Boolean> FILTER_RELATIVE = new ConfigKey<>( + public static final ConfigKey<Boolean> FILTER_RELATIVE = new BooleanConfigKey( "filter.relative", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Time limit for the filtering in seconds. If the time difference between the last position was received by server * and a new position is received by server is more than this limit, the new position will not be filtered out. */ - public static final ConfigKey<Long> FILTER_SKIP_LIMIT = new ConfigKey<>( + public static final ConfigKey<Long> FILTER_SKIP_LIMIT = new LongConfigKey( "filter.skipLimit", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Enable attributes skipping. Attribute skipping can be enabled in the config or device attributes. * If position contains any attribute mentioned in "filter.skipAttributes" config key, position is not filtered out. */ - public static final ConfigKey<Boolean> FILTER_SKIP_ATTRIBUTES_ENABLE = new ConfigKey<>( + public static final ConfigKey<Boolean> FILTER_SKIP_ATTRIBUTES_ENABLE = new BooleanConfigKey( "filter.skipAttributes.enable", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); + + /** + * Attribute skipping can be enabled in the config or device attributes. + * If position contains any attribute mentioned in "filter.skipAttributes" config key, position is not filtered out. + */ + public static final ConfigKey<String> FILTER_SKIP_ATTRIBUTES = new StringConfigKey( + "filter.skipAttributes", + List.of(KeyType.CONFIG, KeyType.DEVICE), + ""); /** * Override device time. Possible values are 'deviceTime' and 'serverTime' */ - public static final ConfigKey<String> TIME_OVERRIDE = new ConfigKey<>( + public static final ConfigKey<String> TIME_OVERRIDE = new StringConfigKey( "time.override", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * List of protocols for overriding time. If not specified override is applied globally. List consist of protocol * names that can be separated by comma or single space character. */ - public static final ConfigKey<String> TIME_PROTOCOLS = new ConfigKey<>( + public static final ConfigKey<String> TIME_PROTOCOLS = new StringConfigKey( "time.protocols", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Replaces coordinates with last known if change is less than a 'coordinates.minError' meters * or more than a 'coordinates.maxError' meters. Helps to avoid coordinates jumps during parking period * or jumps to zero coordinates. */ - public static final ConfigKey<Boolean> COORDINATES_FILTER = new ConfigKey<>( + public static final ConfigKey<Boolean> COORDINATES_FILTER = new BooleanConfigKey( "coordinates.filter", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Distance in meters. Distances below this value gets handled like explained in 'coordinates.filter'. */ - public static final ConfigKey<Integer> COORDINATES_MIN_ERROR = new ConfigKey<>( + public static final ConfigKey<Integer> COORDINATES_MIN_ERROR = new IntegerConfigKey( "coordinates.minError", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Distance in meters. Distances above this value gets handled like explained in 'coordinates.filter', but only if * Position is also marked as 'invalid'. */ - public static final ConfigKey<Integer> COORDINATES_MAX_ERROR = new ConfigKey<>( + public static final ConfigKey<Integer> COORDINATES_MAX_ERROR = new IntegerConfigKey( "coordinates.maxError", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Enable to save device IP addresses information. Disabled by default. */ - public static final ConfigKey<Boolean> PROCESSING_REMOTE_ADDRESS_ENABLE = new ConfigKey<>( + public static final ConfigKey<Boolean> PROCESSING_REMOTE_ADDRESS_ENABLE = new BooleanConfigKey( "processing.remoteAddress.enable", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Enable copying of missing attributes from last position to the current one. Might be useful if device doesn't * send some values in every message. */ - public static final ConfigKey<Boolean> PROCESSING_COPY_ATTRIBUTES_ENABLE = new ConfigKey<>( + public static final ConfigKey<Boolean> PROCESSING_COPY_ATTRIBUTES_ENABLE = new BooleanConfigKey( "processing.copyAttributes.enable", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Enable computed attributes processing. */ - public static final ConfigKey<Boolean> PROCESSING_COMPUTED_ATTRIBUTES_DEVICE_ATTRIBUTES = new ConfigKey<>( + public static final ConfigKey<Boolean> PROCESSING_COMPUTED_ATTRIBUTES_DEVICE_ATTRIBUTES = new BooleanConfigKey( "processing.computedAttributes.deviceAttributes", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Boolean flag to enable or disable reverse geocoder. */ - public static final ConfigKey<Boolean> GEOCODER_ENABLE = new ConfigKey<>( + public static final ConfigKey<Boolean> GEOCODER_ENABLE = new BooleanConfigKey( "geocoder.enable", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Reverse geocoder type. Check reverse geocoding documentation for more info. By default (if the value is not * specified) server uses Google API. */ - public static final ConfigKey<String> GEOCODER_TYPE = new ConfigKey<>( + public static final ConfigKey<String> GEOCODER_TYPE = new StringConfigKey( "geocoder.type", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Geocoder server URL. Applicable only to Nominatim and Gisgraphy providers. */ - public static final ConfigKey<String> GEOCODER_URL = new ConfigKey<>( + public static final ConfigKey<String> GEOCODER_URL = new StringConfigKey( "geocoder.url", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * App id for use with Here provider. */ - public static final ConfigKey<String> GEOCODER_ID = new ConfigKey<>( + public static final ConfigKey<String> GEOCODER_ID = new StringConfigKey( "geocoder.id", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Provider API key. Most providers require API keys. */ - public static final ConfigKey<String> GEOCODER_KEY = new ConfigKey<>( + public static final ConfigKey<String> GEOCODER_KEY = new StringConfigKey( "geocoder.key", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Language parameter for providers that support localization (e.g. Google and Nominatim). */ - public static final ConfigKey<String> GEOCODER_LANGUAGE = new ConfigKey<>( + public static final ConfigKey<String> GEOCODER_LANGUAGE = new StringConfigKey( "geocoder.language", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Address format string. Default value is %h %r, %t, %s, %c. See AddressFormat for more info. */ - public static final ConfigKey<String> GEOCODER_FORMAT = new ConfigKey<>( + public static final ConfigKey<String> GEOCODER_FORMAT = new StringConfigKey( "geocoder.format", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Cache size for geocoding results. */ - public static final ConfigKey<Integer> GEOCODER_CACHE_SIZE = new ConfigKey<>( + public static final ConfigKey<Integer> GEOCODER_CACHE_SIZE = new IntegerConfigKey( "geocoder.cacheSize", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Disable automatic reverse geocoding requests for all positions. */ - public static final ConfigKey<Boolean> GEOCODER_IGNORE_POSITIONS = new ConfigKey<>( + public static final ConfigKey<Boolean> GEOCODER_IGNORE_POSITIONS = new BooleanConfigKey( "geocoder.ignorePositions", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Boolean flag to apply reverse geocoding to invalid positions. */ - public static final ConfigKey<Boolean> GEOCODER_PROCESS_INVALID_POSITIONS = new ConfigKey<>( + public static final ConfigKey<Boolean> GEOCODER_PROCESS_INVALID_POSITIONS = new BooleanConfigKey( "geocoder.processInvalidPositions", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Optional parameter to specify minimum distance for new reverse geocoding request. If distance is less than * specified value (in meters), then Traccar will reuse last known address. */ - public static final ConfigKey<Integer> GEOCODER_REUSE_DISTANCE = new ConfigKey<>( + public static final ConfigKey<Integer> GEOCODER_REUSE_DISTANCE = new IntegerConfigKey( "geocoder.reuseDistance", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Perform geocoding when preparing reports and sending notifications. */ - public static final ConfigKey<Boolean> GEOCODER_ON_REQUEST = new ConfigKey<>( + public static final ConfigKey<Boolean> GEOCODER_ON_REQUEST = new BooleanConfigKey( "geocoder.onRequest", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Boolean flag to enable LBS location resolution. Some devices send cell towers information and WiFi point when GPS * location is not available. Traccar can determine coordinates based on that information using third party * services. Default value is false. */ - public static final ConfigKey<Boolean> GEOLOCATION_ENABLE = new ConfigKey<>( + public static final ConfigKey<Boolean> GEOLOCATION_ENABLE = new BooleanConfigKey( "geolocation.enable", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Provider to use for LBS location. Available options: google, mozilla and opencellid. By default opencellid is * used. You have to supply a key that you get from corresponding provider. For more information see LBS geolocation * documentation. */ - public static final ConfigKey<String> GEOLOCATION_TYPE = new ConfigKey<>( + public static final ConfigKey<String> GEOLOCATION_TYPE = new StringConfigKey( "geolocation.type", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Geolocation provider API URL address. Not required for most providers. */ - public static final ConfigKey<String> GEOLOCATION_URL = new ConfigKey<>( + public static final ConfigKey<String> GEOLOCATION_URL = new StringConfigKey( "geolocation.url", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Provider API key. OpenCellID service requires API key. */ - public static final ConfigKey<String> GEOLOCATION_KEY = new ConfigKey<>( + public static final ConfigKey<String> GEOLOCATION_KEY = new StringConfigKey( "geolocation.key", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Boolean flag to apply geolocation to invalid positions. */ - public static final ConfigKey<Boolean> GEOLOCATION_PROCESS_INVALID_POSITIONS = new ConfigKey<>( + public static final ConfigKey<Boolean> GEOLOCATION_PROCESS_INVALID_POSITIONS = new BooleanConfigKey( "geolocation.processInvalidPositions", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Default MCC value to use if device doesn't report MCC. */ - public static final ConfigKey<Integer> GEOLOCATION_MCC = new ConfigKey<>( + public static final ConfigKey<Integer> GEOLOCATION_MCC = new IntegerConfigKey( "geolocation.mcc", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Default MNC value to use if device doesn't report MNC. */ - public static final ConfigKey<Integer> GEOLOCATION_MNC = new ConfigKey<>( + public static final ConfigKey<Integer> GEOLOCATION_MNC = new IntegerConfigKey( "geolocation.mnc", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Boolean flag to enable speed limit API to get speed limit values depending on location. Default value is false. */ - public static final ConfigKey<Boolean> SPEED_LIMIT_ENABLE = new ConfigKey<>( + public static final ConfigKey<Boolean> SPEED_LIMIT_ENABLE = new BooleanConfigKey( "speedLimit.enable", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Provider to use for speed limit. Available options: overpass. By default overpass is used. */ - public static final ConfigKey<String> SPEED_LIMIT_TYPE = new ConfigKey<>( + public static final ConfigKey<String> SPEED_LIMIT_TYPE = new StringConfigKey( "speedLimit.type", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Speed limit provider API URL address. */ - public static final ConfigKey<String> SPEED_LIMIT_URL = new ConfigKey<>( + public static final ConfigKey<String> SPEED_LIMIT_URL = new StringConfigKey( "speedLimit.url", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Override latitude sign / hemisphere. Useful in cases where value is incorrect because of device bug. Value can be * N for North or S for South. */ - public static final ConfigKey<String> LOCATION_LATITUDE_HEMISPHERE = new ConfigKey<>( + public static final ConfigKey<String> LOCATION_LATITUDE_HEMISPHERE = new StringConfigKey( "location.latitudeHemisphere", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Override longitude sign / hemisphere. Useful in cases where value is incorrect because of device bug. Value can * be E for East or W for West. */ - public static final ConfigKey<String> LOCATION_LONGITUDE_HEMISPHERE = new ConfigKey<>( + public static final ConfigKey<String> LOCATION_LONGITUDE_HEMISPHERE = new StringConfigKey( "location.longitudeHemisphere", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Jetty Request Log Path. @@ -1220,112 +1301,112 @@ public final class Keys { * over the file. * Example: ./logs/jetty-yyyy_mm_dd.request.log */ - public static final ConfigKey<String> WEB_REQUEST_LOG_PATH = new ConfigKey<>( + public static final ConfigKey<String> WEB_REQUEST_LOG_PATH = new StringConfigKey( "web.requestLog.path", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Set the number of days before rotated request log files are deleted. */ - public static final ConfigKey<Integer> WEB_REQUEST_LOG_RETAIN_DAYS = new ConfigKey<>( + public static final ConfigKey<Integer> WEB_REQUEST_LOG_RETAIN_DAYS = new IntegerConfigKey( "web.requestLog.retainDays", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Disable systemd health checks. */ - public static final ConfigKey<Boolean> WEB_DISABLE_HEALTH_CHECK = new ConfigKey<>( + public static final ConfigKey<Boolean> WEB_DISABLE_HEALTH_CHECK = new BooleanConfigKey( "web.disableHealthCheck", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Sets SameSite cookie attribute value. * Supported options: Lax, Strict, None. */ - public static final ConfigKey<String> WEB_SAME_SITE_COOKIE = new ConfigKey<>( + public static final ConfigKey<String> WEB_SAME_SITE_COOKIE = new StringConfigKey( "web.sameSiteCookie", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Enables persisting Jetty session to the database */ - public static final ConfigKey<Boolean> WEB_PERSIST_SESSION = new ConfigKey<>( + public static final ConfigKey<Boolean> WEB_PERSIST_SESSION = new BooleanConfigKey( "web.persistSession", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Public URL for the web app. Used for notification and report link. * * If not provided, Traccar will attempt to get a URL from the server IP address, but it might be a local address. */ - public static final ConfigKey<String> WEB_URL = new ConfigKey<>( + public static final ConfigKey<String> WEB_URL = new StringConfigKey( "web.url", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Output logging to the standard terminal output instead of a log file. */ - public static final ConfigKey<Boolean> LOGGER_CONSOLE = new ConfigKey<>( + public static final ConfigKey<Boolean> LOGGER_CONSOLE = new BooleanConfigKey( "logger.console", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Log executed SQL queries. */ - public static final ConfigKey<Boolean> LOGGER_QUERIES = new ConfigKey<>( + public static final ConfigKey<Boolean> LOGGER_QUERIES = new BooleanConfigKey( "logger.queries", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Log file name. For rotating logs, a date is added at the end of the file name for non-current logs. */ - public static final ConfigKey<String> LOGGER_FILE = new ConfigKey<>( + public static final ConfigKey<String> LOGGER_FILE = new StringConfigKey( "logger.file", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Logging level. Default value is 'info'. * Available options: off, severe, warning, info, config, fine, finer, finest, all. */ - public static final ConfigKey<String> LOGGER_LEVEL = new ConfigKey<>( + public static final ConfigKey<String> LOGGER_LEVEL = new StringConfigKey( "logger.level", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Print full exception traces. Useful for debugging. By default shortened traces are logged. */ - public static final ConfigKey<Boolean> LOGGER_FULL_STACK_TRACES = new ConfigKey<>( + public static final ConfigKey<Boolean> LOGGER_FULL_STACK_TRACES = new BooleanConfigKey( "logger.fullStackTraces", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Create a new log file daily. Helps with log management. For example, downloading and cleaning logs. Enabled by * default. */ - public static final ConfigKey<Boolean> LOGGER_ROTATE = new ConfigKey<>( + public static final ConfigKey<Boolean> LOGGER_ROTATE = new BooleanConfigKey( "logger.rotate", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * A list of position attributes to log. */ - public static final ConfigKey<String> LOGGER_ATTRIBUTES = new ConfigKey<>( + public static final ConfigKey<String> LOGGER_ATTRIBUTES = new StringConfigKey( "logger.attributes", - Collections.singletonList(KeyType.CONFIG), + List.of(KeyType.CONFIG), "time,position,speed,course,accuracy,result"); /** * Multicast address for broadcasting synchronization events. */ - public static final ConfigKey<String> BROADCAST_ADDRESS = new ConfigKey<>( + public static final ConfigKey<String> BROADCAST_ADDRESS = new StringConfigKey( "broadcast.address", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); /** * Multicast port for broadcasting synchronization events. */ - public static final ConfigKey<Integer> BROADCAST_PORT = new ConfigKey<>( + public static final ConfigKey<Integer> BROADCAST_PORT = new IntegerConfigKey( "broadcast.port", - Collections.singletonList(KeyType.CONFIG)); + List.of(KeyType.CONFIG)); } diff --git a/src/main/java/org/traccar/database/DeviceManager.java b/src/main/java/org/traccar/database/DeviceManager.java index 81043fd7a..a3e04f920 100644 --- a/src/main/java/org/traccar/database/DeviceManager.java +++ b/src/main/java/org/traccar/database/DeviceManager.java @@ -73,34 +73,6 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity refreshLastPositions(); } - @Override - public Device addUnknownDevice(String uniqueId) { - Device device = new Device(); - device.setName(uniqueId); - device.setUniqueId(uniqueId); - device.setCategory(Context.getConfig().getString(Keys.DATABASE_REGISTER_UNKNOWN_DEFAULT_CATEGORY)); - - long defaultGroupId = Context.getConfig().getLong(Keys.DATABASE_REGISTER_UNKNOWN_DEFAULT_GROUP_ID); - if (defaultGroupId != 0) { - device.setGroupId(defaultGroupId); - } - - try { - addItem(device); - - LOGGER.info("Automatically registered device " + uniqueId); - - if (defaultGroupId != 0) { - Context.getPermissionsManager().refreshDeviceAndGroupPermissions(); - } - - return device; - } catch (StorageException e) { - LOGGER.warn("Automatic device registration error", e); - return null; - } - } - public void updateDeviceCache(boolean force) { long lastUpdate = devicesLastUpdate.get(); if ((force || System.currentTimeMillis() - lastUpdate > dataRefreshDelay) @@ -316,35 +288,6 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity return result != null ? (String) result : defaultValue; } - @Override - public int lookupAttributeInteger( - long deviceId, String attributeName, int defaultValue, boolean lookupServer, boolean lookupConfig) { - Object result = lookupAttribute(deviceId, attributeName, lookupServer, lookupConfig); - if (result != null) { - return result instanceof String ? Integer.parseInt((String) result) : ((Number) result).intValue(); - } - return defaultValue; - } - - @Override - public long lookupAttributeLong( - long deviceId, String attributeName, long defaultValue, boolean lookupServer, boolean lookupConfig) { - Object result = lookupAttribute(deviceId, attributeName, lookupServer, lookupConfig); - if (result != null) { - return result instanceof String ? Long.parseLong((String) result) : ((Number) result).longValue(); - } - return defaultValue; - } - - public double lookupAttributeDouble( - long deviceId, String attributeName, double defaultValue, boolean lookupServer, boolean lookupConfig) { - Object result = lookupAttribute(deviceId, attributeName, lookupServer, lookupConfig); - if (result != null) { - return result instanceof String ? Double.parseDouble((String) result) : ((Number) result).doubleValue(); - } - return defaultValue; - } - private Object lookupAttribute(long deviceId, String attributeName, boolean lookupServer, boolean lookupConfig) { Object result = null; Device device = getById(deviceId); diff --git a/src/main/java/org/traccar/database/IdentityManager.java b/src/main/java/org/traccar/database/IdentityManager.java index ee386fdfd..10a64ebd9 100644 --- a/src/main/java/org/traccar/database/IdentityManager.java +++ b/src/main/java/org/traccar/database/IdentityManager.java @@ -20,8 +20,6 @@ import org.traccar.model.Position; public interface IdentityManager { - Device addUnknownDevice(String uniqueId); - Device getById(long id); Device getByUniqueId(String uniqueId) throws Exception; @@ -38,13 +36,4 @@ public interface IdentityManager { String lookupAttributeString( long deviceId, String attributeName, String defaultValue, boolean lookupServer, boolean lookupConfig); - int lookupAttributeInteger( - long deviceId, String attributeName, int defaultValue, boolean lookupServer, boolean lookupConfig); - - long lookupAttributeLong( - long deviceId, String attributeName, long defaultValue, boolean lookupServer, boolean lookupConfig); - - double lookupAttributeDouble( - long deviceId, String attributeName, double defaultValue, boolean lookupServer, boolean lookupConfig); - } diff --git a/src/main/java/org/traccar/handler/FilterHandler.java b/src/main/java/org/traccar/handler/FilterHandler.java index 0511ec98b..00cbf92a0 100644 --- a/src/main/java/org/traccar/handler/FilterHandler.java +++ b/src/main/java/org/traccar/handler/FilterHandler.java @@ -21,11 +21,18 @@ import org.slf4j.LoggerFactory; import org.traccar.BaseDataHandler; import org.traccar.config.Config; import org.traccar.config.Keys; -import org.traccar.database.DataManager; -import org.traccar.database.IdentityManager; import org.traccar.helper.UnitsConverter; +import org.traccar.helper.model.AttributeUtil; +import org.traccar.model.Device; import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; +import org.traccar.storage.Storage; import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Limit; +import org.traccar.storage.query.Order; +import org.traccar.storage.query.Request; import javax.inject.Inject; import java.util.Date; @@ -50,11 +57,11 @@ public class FilterHandler extends BaseDataHandler { private final long skipLimit; private final boolean skipAttributes; - private final IdentityManager identityManager; - private final DataManager dataManager; + private final CacheManager cacheManager; + private final Storage storage; @Inject - public FilterHandler(Config config, IdentityManager identityManager, DataManager dataManager) { + public FilterHandler(Config config, CacheManager cacheManager, Storage storage) { enabled = config.getBoolean(Keys.FILTER_ENABLE); filterInvalid = config.getBoolean(Keys.FILTER_INVALID); filterZero = config.getBoolean(Keys.FILTER_ZERO); @@ -69,8 +76,18 @@ public class FilterHandler extends BaseDataHandler { filterRelative = config.getBoolean(Keys.FILTER_RELATIVE); skipLimit = config.getLong(Keys.FILTER_SKIP_LIMIT) * 1000; skipAttributes = config.getBoolean(Keys.FILTER_SKIP_ATTRIBUTES_ENABLE); - this.identityManager = identityManager; - this.dataManager = dataManager; + this.cacheManager = cacheManager; + this.storage = storage; + } + + private Position getPrecedingPosition(long deviceId, Date date) throws StorageException { + return storage.getObject(Position.class, new Request( + new Columns.All(), + new Condition.And( + new Condition.Equals("deviceId", "deviceId", deviceId), + new Condition.Compare("fixTime", "<=", "time", date)), + new Order(true, "fixTime"), + new Limit(1))); } private boolean filterInvalid(Position position) { @@ -144,9 +161,8 @@ public class FilterHandler extends BaseDataHandler { private boolean skipAttributes(Position position) { if (skipAttributes) { - String attributesString = identityManager.lookupAttributeString( - position.getDeviceId(), "filter.skipAttributes", "", false, true); - for (String attribute : attributesString.split("[ ,]")) { + String string = AttributeUtil.lookup(cacheManager, Keys.FILTER_SKIP_ATTRIBUTES, position.getDeviceId()); + for (String attribute : string.split("[ ,]")) { if (position.getAttributes().containsKey(attribute)) { return true; } @@ -183,13 +199,13 @@ public class FilterHandler extends BaseDataHandler { if (filterRelative) { try { Date newFixTime = position.getFixTime(); - preceding = dataManager.getPrecedingPosition(deviceId, newFixTime); + preceding = getPrecedingPosition(deviceId, newFixTime); } catch (StorageException e) { LOGGER.warn("Error retrieving preceding position; fallbacking to last received position.", e); - preceding = getLastReceivedPosition(deviceId); + preceding = cacheManager.getPosition(deviceId); } } else { - preceding = getLastReceivedPosition(deviceId); + preceding = cacheManager.getPosition(deviceId); } if (filterDuplicate(position, preceding) && !skipLimit(position, preceding) && !skipAttributes(position)) { filterType.append("Duplicate "); @@ -209,7 +225,7 @@ public class FilterHandler extends BaseDataHandler { } if (filterType.length() > 0) { - String uniqueId = identityManager.getById(deviceId).getUniqueId(); + String uniqueId = cacheManager.getObject(Device.class, deviceId).getUniqueId(); LOGGER.info("Position filtered by {}filters from device: {}", filterType, uniqueId); return true; } @@ -217,10 +233,6 @@ public class FilterHandler extends BaseDataHandler { return false; } - private Position getLastReceivedPosition(long deviceId) { - return identityManager.getLastPosition(deviceId); - } - @Override protected Position handlePosition(Position position) { if (enabled && filter(position)) { diff --git a/src/main/java/org/traccar/handler/HemisphereHandler.java b/src/main/java/org/traccar/handler/HemisphereHandler.java index 2e3ed9d91..f760457a3 100644 --- a/src/main/java/org/traccar/handler/HemisphereHandler.java +++ b/src/main/java/org/traccar/handler/HemisphereHandler.java @@ -39,7 +39,7 @@ public class HemisphereHandler extends BaseDataHandler { latitudeFactor = -1; } } - String longitudeHemisphere = config.getString(Keys.LOCATION_LATITUDE_HEMISPHERE); + String longitudeHemisphere = config.getString(Keys.LOCATION_LONGITUDE_HEMISPHERE); if (longitudeHemisphere != null) { if (longitudeHemisphere.equalsIgnoreCase("E")) { longitudeFactor = 1; diff --git a/src/main/java/org/traccar/handler/events/FuelDropEventHandler.java b/src/main/java/org/traccar/handler/events/FuelDropEventHandler.java index 7849abff9..2d105af3e 100644 --- a/src/main/java/org/traccar/handler/events/FuelDropEventHandler.java +++ b/src/main/java/org/traccar/handler/events/FuelDropEventHandler.java @@ -16,10 +16,13 @@ package org.traccar.handler.events; import io.netty.channel.ChannelHandler; -import org.traccar.database.IdentityManager; +import org.traccar.config.Keys; +import org.traccar.helper.model.AttributeUtil; +import org.traccar.helper.model.PositionUtil; import org.traccar.model.Device; import org.traccar.model.Event; import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; import javax.inject.Inject; import java.util.Collections; @@ -28,31 +31,28 @@ import java.util.Map; @ChannelHandler.Sharable public class FuelDropEventHandler extends BaseEventHandler { - public static final String ATTRIBUTE_FUEL_DROP_THRESHOLD = "fuelDropThreshold"; - - private final IdentityManager identityManager; + private final CacheManager cacheManager; @Inject - public FuelDropEventHandler(IdentityManager identityManager) { - this.identityManager = identityManager; + public FuelDropEventHandler(CacheManager cacheManager) { + this.cacheManager = cacheManager; } @Override protected Map<Event, Position> analyzePosition(Position position) { - Device device = identityManager.getById(position.getDeviceId()); + Device device = cacheManager.getObject(Device.class, position.getDeviceId()); if (device == null) { return null; } - if (!identityManager.isLatestPosition(position)) { + if (!PositionUtil.isLatest(cacheManager, position)) { return null; } - double fuelDropThreshold = identityManager - .lookupAttributeDouble(device.getId(), ATTRIBUTE_FUEL_DROP_THRESHOLD, 0, true, false); - + double fuelDropThreshold = AttributeUtil.lookup( + cacheManager, Keys.EVENT_FUEL_DROP_THRESHOLD, position.getDeviceId()); if (fuelDropThreshold > 0) { - Position lastPosition = identityManager.getLastPosition(position.getDeviceId()); + Position lastPosition = cacheManager.getPosition(position.getDeviceId()); if (position.getAttributes().containsKey(Position.KEY_FUEL_LEVEL) && lastPosition != null && lastPosition.getAttributes().containsKey(Position.KEY_FUEL_LEVEL)) { @@ -60,7 +60,6 @@ public class FuelDropEventHandler extends BaseEventHandler { - position.getDouble(Position.KEY_FUEL_LEVEL); if (drop >= fuelDropThreshold) { Event event = new Event(Event.TYPE_DEVICE_FUEL_DROP, position); - event.set(ATTRIBUTE_FUEL_DROP_THRESHOLD, fuelDropThreshold); return Collections.singletonMap(event, position); } } diff --git a/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java b/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java index 45bb13be5..6de56d11e 100644 --- a/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java +++ b/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java @@ -23,6 +23,8 @@ import io.netty.channel.ChannelHandler; import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.database.DeviceManager; +import org.traccar.helper.model.AttributeUtil; +import org.traccar.helper.model.PositionUtil; import org.traccar.model.Device; import org.traccar.session.DeviceState; import org.traccar.model.Event; @@ -36,7 +38,6 @@ import javax.inject.Inject; public class OverspeedEventHandler extends BaseEventHandler { public static final String ATTRIBUTE_SPEED = "speed"; - public static final String ATTRIBUTE_SPEED_LIMIT = "speedLimit"; private final DeviceManager deviceManager; private final CacheManager cacheManager; @@ -58,7 +59,7 @@ public class OverspeedEventHandler extends BaseEventHandler { Position position = deviceState.getOverspeedPosition(); Event event = new Event(Event.TYPE_DEVICE_OVERSPEED, position); event.set(ATTRIBUTE_SPEED, deviceState.getOverspeedPosition().getSpeed()); - event.set(ATTRIBUTE_SPEED_LIMIT, speedLimit); + event.set(Position.KEY_SPEED_LIMIT, speedLimit); event.setGeofenceId(deviceState.getOverspeedGeofenceId()); deviceState.setOverspeedState(notRepeat); deviceState.setOverspeedPosition(null); @@ -115,15 +116,15 @@ public class OverspeedEventHandler extends BaseEventHandler { protected Map<Event, Position> analyzePosition(Position position) { long deviceId = position.getDeviceId(); - Device device = deviceManager.getById(deviceId); + Device device = cacheManager.getObject(Device.class, position.getDeviceId()); if (device == null) { return null; } - if (!deviceManager.isLatestPosition(position) || !position.getValid()) { + if (!PositionUtil.isLatest(cacheManager, position) || !position.getValid()) { return null; } - double speedLimit = deviceManager.lookupAttributeDouble(deviceId, ATTRIBUTE_SPEED_LIMIT, 0, true, false); + double speedLimit = AttributeUtil.lookup(cacheManager, Keys.EVENT_OVERSPEED_LIMIT, deviceId); double positionSpeedLimit = position.getDouble(Position.KEY_SPEED_LIMIT); if (positionSpeedLimit > 0) { @@ -137,7 +138,7 @@ public class OverspeedEventHandler extends BaseEventHandler { for (long geofenceId : device.getGeofenceIds()) { Geofence geofence = cacheManager.getObject(Geofence.class, geofenceId); if (geofence != null) { - double currentSpeedLimit = geofence.getDouble(ATTRIBUTE_SPEED_LIMIT); + double currentSpeedLimit = geofence.getDouble(Keys.EVENT_OVERSPEED_LIMIT.getKey()); if (currentSpeedLimit > 0 && geofenceSpeedLimit == 0 || preferLowest && currentSpeedLimit < geofenceSpeedLimit || !preferLowest && currentSpeedLimit > geofenceSpeedLimit) { diff --git a/src/main/java/org/traccar/helper/model/AttributeUtil.java b/src/main/java/org/traccar/helper/model/AttributeUtil.java new file mode 100644 index 000000000..5b3fc1cbe --- /dev/null +++ b/src/main/java/org/traccar/helper/model/AttributeUtil.java @@ -0,0 +1,72 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed 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.traccar.helper.model; + +import org.traccar.config.ConfigKey; +import org.traccar.config.KeyType; +import org.traccar.model.Device; +import org.traccar.model.Group; +import org.traccar.session.cache.CacheManager; + +public final class AttributeUtil { + + private AttributeUtil() { + } + + @SuppressWarnings({ "deprecation", "unchecked" }) + public static <T> T lookup(CacheManager cacheManager, ConfigKey<T> key, long deviceId) { + Device device = cacheManager.getObject(Device.class, deviceId); + Object result = device.getAttributes().get(key.getKey()); + long groupId = device.getGroupId(); + while (result == null && groupId > 0) { + Group group = cacheManager.getObject(Group.class, groupId); + if (group != null) { + result = group.getAttributes().get(key.getKey()); + } + } + if (result == null && key.hasType(KeyType.SERVER)) { + result = cacheManager.getServer().getAttributes().get(key.getKey()); + } + if (result == null && key.hasType(KeyType.CONFIG)) { + result = cacheManager.getConfig().getString(key.getKey()); + } + + if (result != null) { + Class<T> valueClass = key.getValueClass(); + if (valueClass.equals(Boolean.class)) { + return (T) (result instanceof String + ? Boolean.parseBoolean((String) result) + : result); + } else if (valueClass.equals(Integer.class)) { + return (T) (Object) (result instanceof String + ? Integer.parseInt((String) result) + : ((Number) result).intValue()); + } else if (valueClass.equals(Long.class)) { + return (T) (Object) (result instanceof String + ? Long.parseLong((String) result) + : ((Number) result).longValue()); + } else if (valueClass.equals(Double.class)) { + return (T) (Object) (result instanceof String + ? Double.parseDouble((String) result) + : ((Number) result).doubleValue()); + } else { + return (T) result; + } + } + return key.getDefaultValue(); + } + +} diff --git a/src/main/java/org/traccar/model/CellTower.java b/src/main/java/org/traccar/model/CellTower.java index af33b1f5c..16a28ea79 100644 --- a/src/main/java/org/traccar/model/CellTower.java +++ b/src/main/java/org/traccar/model/CellTower.java @@ -38,7 +38,7 @@ public class CellTower { } public static CellTower fromLacCid(Config config, int lac, long cid) { - return from(config.getInteger(Keys.GEOLOCATION_MCC), config.getInteger(Keys.GEOLOCATION_MCC), lac, cid); + return from(config.getInteger(Keys.GEOLOCATION_MCC), config.getInteger(Keys.GEOLOCATION_MNC), lac, cid); } public static CellTower fromCidLac(Config config, long cid, int lac) { diff --git a/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java b/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java index 0ff668fa8..aa23bfac5 100644 --- a/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,8 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; +import org.traccar.config.Keys; +import org.traccar.helper.model.AttributeUtil; import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DataConverter; @@ -66,8 +68,9 @@ public class StarLinkProtocolDecoder extends BaseProtocolDecoder { } public String[] getFormat(long deviceId) { - return getIdentityManager().lookupAttributeString( - deviceId, getProtocolName() + ".format", format, false, false).split(","); + String value = AttributeUtil.lookup( + getCacheManager(), Keys.PROTOCOL_FORMAT.withPrefix(getProtocolName()), deviceId); + return (value != null ? value : format).split(","); } public void setFormat(String format) { @@ -75,8 +78,9 @@ public class StarLinkProtocolDecoder extends BaseProtocolDecoder { } public DateFormat getDateFormat(long deviceId) { - DateFormat dateFormat = new SimpleDateFormat(getIdentityManager().lookupAttributeString( - deviceId, getProtocolName() + ".dateFormat", this.dateFormat, false, false)); + String value = AttributeUtil.lookup( + getCacheManager(), Keys.PROTOCOL_DATE_FORMAT.withPrefix(getProtocolName()), deviceId); + DateFormat dateFormat = new SimpleDateFormat(value != null ? value : this.dateFormat); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); return dateFormat; } diff --git a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java index 6340def86..44ba1da38 100644 --- a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java @@ -20,6 +20,8 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; +import org.traccar.config.Keys; +import org.traccar.helper.model.AttributeUtil; import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -71,8 +73,8 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { } public int getProtocolType(long deviceId) { - return getIdentityManager().lookupAttributeInteger( - deviceId, getProtocolName() + ".protocolType", protocolType, false, true); + Integer value = AttributeUtil.lookup(getCacheManager(), Keys.PROTOCOL_TYPE, deviceId); + return value != null ? value : protocolType; } public void setHbm(boolean hbm) { @@ -80,8 +82,8 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { } public boolean isHbm(long deviceId) { - return getIdentityManager().lookupAttributeBoolean( - deviceId, getProtocolName() + ".hbm", hbm, false, true); + Boolean value = AttributeUtil.lookup(getCacheManager(), Keys.PROTOCOL_HBM, deviceId); + return value != null ? value : hbm; } public void setIncludeAdc(boolean includeAdc) { @@ -89,8 +91,9 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { } public boolean isIncludeAdc(long deviceId) { - return getIdentityManager().lookupAttributeBoolean( - deviceId, getProtocolName() + ".includeAdc", includeAdc, false, true); + Boolean value = AttributeUtil.lookup( + getCacheManager(), Keys.PROTOCOL_INCLUDE_ADC.withPrefix(getProtocolName()), deviceId); + return value != null ? value : includeAdc; } public void setIncludeRpm(boolean includeRpm) { @@ -98,8 +101,9 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { } public boolean isIncludeRpm(long deviceId) { - return getIdentityManager().lookupAttributeBoolean( - deviceId, getProtocolName() + ".includeRpm", includeRpm, false, true); + Boolean value = AttributeUtil.lookup( + getCacheManager(), Keys.PROTOCOL_INCLUDE_RPM.withPrefix(getProtocolName()), deviceId); + return value != null ? value : includeRpm; } public void setIncludeTemp(boolean includeTemp) { @@ -107,8 +111,9 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { } public boolean isIncludeTemp(long deviceId) { - return getIdentityManager().lookupAttributeBoolean( - deviceId, getProtocolName() + ".includeTemp", includeTemp, false, true); + Boolean value = AttributeUtil.lookup( + getCacheManager(), Keys.PROTOCOL_INCLUDE_TEMPERATURE.withPrefix(getProtocolName()), deviceId); + return value != null ? value : includeTemp; } private Position decode9( diff --git a/src/main/java/org/traccar/session/ConnectionManager.java b/src/main/java/org/traccar/session/ConnectionManager.java index 6d5fefa75..3fa467b57 100644 --- a/src/main/java/org/traccar/session/ConnectionManager.java +++ b/src/main/java/org/traccar/session/ConnectionManager.java @@ -28,6 +28,7 @@ import org.traccar.config.Keys; import org.traccar.database.NotificationManager; import org.traccar.handler.events.MotionEventHandler; import org.traccar.handler.events.OverspeedEventHandler; +import org.traccar.helper.model.AttributeUtil; import org.traccar.model.Device; import org.traccar.model.Event; import org.traccar.model.Position; @@ -279,9 +280,9 @@ public class ConnectionManager { result.putAll(event); } + double speedLimit = AttributeUtil.lookup(cacheManager, Keys.EVENT_OVERSPEED_LIMIT, deviceId); event = Main.getInjector().getInstance(OverspeedEventHandler.class) - .updateOverspeedState(deviceState, Context.getDeviceManager(). - lookupAttributeDouble(deviceId, OverspeedEventHandler.ATTRIBUTE_SPEED_LIMIT, 0, true, false)); + .updateOverspeedState(deviceState, speedLimit); if (event != null) { result.putAll(event); } diff --git a/src/main/java/org/traccar/session/cache/CacheManager.java b/src/main/java/org/traccar/session/cache/CacheManager.java index 896df83e7..87384f746 100644 --- a/src/main/java/org/traccar/session/cache/CacheManager.java +++ b/src/main/java/org/traccar/session/cache/CacheManager.java @@ -74,6 +74,10 @@ public class CacheManager { invalidateUsers(); } + public Config getConfig() { + return config; + } + public <T extends BaseModel> T getObject(Class<T> clazz, long id) { try { lock.readLock().lock(); diff --git a/src/test/java/org/traccar/BaseTest.java b/src/test/java/org/traccar/BaseTest.java index 1652a6694..add423cdd 100644 --- a/src/test/java/org/traccar/BaseTest.java +++ b/src/test/java/org/traccar/BaseTest.java @@ -9,6 +9,7 @@ import org.traccar.database.StatisticsManager; import org.traccar.model.Device; import org.traccar.session.ConnectionManager; import org.traccar.session.DeviceSession; +import org.traccar.session.cache.CacheManager; import java.net.SocketAddress; import java.util.HashSet; @@ -16,6 +17,7 @@ import java.util.HashSet; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -23,19 +25,18 @@ import static org.mockito.Mockito.when; public class BaseTest { protected <T extends BaseProtocolDecoder> T inject(T decoder) throws Exception { - decoder.setConfig(new Config()); + var config = new Config(); + decoder.setConfig(config); var device = mock(Device.class); when(device.getId()).thenReturn(1L); var identityManager = mock(IdentityManager.class); when(identityManager.getById(anyLong())).thenReturn(device); when(identityManager.getByUniqueId(any())).thenReturn(device); - when(identityManager.lookupAttributeBoolean(anyLong(), any(), anyBoolean(), anyBoolean(), anyBoolean())) - .thenAnswer(invocation -> invocation.getArguments()[2]); - when(identityManager.lookupAttributeString(anyLong(), any(), any(), anyBoolean(), anyBoolean())) - .thenAnswer(invocation -> invocation.getArguments()[2]); - when(identityManager.lookupAttributeInteger(anyLong(), any(), anyInt(), anyBoolean(), anyBoolean())) - .thenAnswer(invocation -> invocation.getArguments()[2]); decoder.setIdentityManager(identityManager); + var cacheManager = mock(CacheManager.class); + when(cacheManager.getConfig()).thenReturn(config); + when(cacheManager.getObject(eq(Device.class), anyLong())).thenReturn(device); + decoder.setCacheManager(cacheManager); var connectionManager = mock(ConnectionManager.class); var uniqueIdsProvided = new HashSet<Boolean>(); when(connectionManager.getDeviceSession(any(), any(), any(), any())).thenAnswer(invocation -> { diff --git a/src/test/java/org/traccar/handler/FilterHandlerTest.java b/src/test/java/org/traccar/handler/FilterHandlerTest.java index 10d6768cf..a1102da88 100644 --- a/src/test/java/org/traccar/handler/FilterHandlerTest.java +++ b/src/test/java/org/traccar/handler/FilterHandlerTest.java @@ -5,15 +5,16 @@ import org.junit.Test; import org.traccar.BaseTest; import org.traccar.config.Config; import org.traccar.config.Keys; -import org.traccar.database.DataManager; -import org.traccar.database.IdentityManager; import org.traccar.model.Device; import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; import java.util.Date; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -26,9 +27,9 @@ public class FilterHandlerTest extends BaseTest { public void passingHandler() { var config = mock(Config.class); when(config.getBoolean(Keys.FILTER_ENABLE)).thenReturn(true); - var identityManager = mock(IdentityManager.class); - var dataManager = mock(DataManager.class); - passingHandler = new FilterHandler(config, identityManager, dataManager); + var cacheManager = mock(CacheManager.class); + when(cacheManager.getConfig()).thenReturn(config); + passingHandler = new FilterHandler(config, cacheManager, null); } @Before @@ -45,11 +46,11 @@ public class FilterHandlerTest extends BaseTest { when(config.getInteger(Keys.FILTER_MAX_SPEED)).thenReturn(500); when(config.getLong(Keys.FILTER_SKIP_LIMIT)).thenReturn(10L); when(config.getBoolean(Keys.FILTER_SKIP_ATTRIBUTES_ENABLE)).thenReturn(true); - var identityManager = mock(IdentityManager.class); - when(identityManager.lookupAttributeString(0, "filter.skipAttributes", "", false, true)).thenReturn("alarm,result"); - when(identityManager.getById(0)).thenReturn(mock(Device.class)); - var dataManager = mock(DataManager.class); - filteringHandler = new FilterHandler(config, identityManager, dataManager); + when(config.getString(Keys.FILTER_SKIP_ATTRIBUTES.getKey())).thenReturn("alarm,result"); + var cacheManager = mock(CacheManager.class); + when(cacheManager.getConfig()).thenReturn(config); + when(cacheManager.getObject(any(), anyLong())).thenReturn(mock(Device.class)); + filteringHandler = new FilterHandler(config, cacheManager, null); } private Position createPosition(Date time, boolean valid, double speed) { diff --git a/src/test/java/org/traccar/handler/events/OverspeedEventHandlerTest.java b/src/test/java/org/traccar/handler/events/OverspeedEventHandlerTest.java index 9e86031e8..46e142935 100644 --- a/src/test/java/org/traccar/handler/events/OverspeedEventHandlerTest.java +++ b/src/test/java/org/traccar/handler/events/OverspeedEventHandlerTest.java @@ -61,7 +61,7 @@ public class OverspeedEventHandlerTest extends BaseTest { Event event = events.keySet().iterator().next(); assertEquals(Event.TYPE_DEVICE_OVERSPEED, event.getType()); assertEquals(50, event.getDouble("speed"), 0.1); - assertEquals(40, event.getDouble(OverspeedEventHandler.ATTRIBUTE_SPEED_LIMIT), 0.1); + assertEquals(40, event.getDouble("speedLimit"), 0.1); assertEquals(geofenceId, event.getGeofenceId()); assertEquals(notRepeat, deviceState.getOverspeedState()); |