aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/traccar/helper
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/traccar/helper')
-rw-r--r--src/main/java/org/traccar/helper/BitUtil.java4
-rw-r--r--src/main/java/org/traccar/helper/BufferUtil.java48
-rw-r--r--src/main/java/org/traccar/helper/Checksum.java15
-rw-r--r--src/main/java/org/traccar/helper/ClassScanner.java2
-rw-r--r--src/main/java/org/traccar/helper/Log.java36
-rw-r--r--src/main/java/org/traccar/helper/LogAction.java17
-rw-r--r--src/main/java/org/traccar/helper/NetworkUtil.java31
-rw-r--r--src/main/java/org/traccar/helper/ObdDecoder.java36
-rw-r--r--src/main/java/org/traccar/helper/ObjectMapperContextResolver.java38
-rw-r--r--src/main/java/org/traccar/helper/Parser.java29
-rw-r--r--src/main/java/org/traccar/helper/PatternUtil.java2
-rw-r--r--src/main/java/org/traccar/helper/StringUtil.java32
-rw-r--r--src/main/java/org/traccar/helper/WebHelper.java (renamed from src/main/java/org/traccar/helper/ServletHelper.java)26
-rw-r--r--src/main/java/org/traccar/helper/model/AttributeUtil.java188
-rw-r--r--src/main/java/org/traccar/helper/model/DeviceUtil.java79
-rw-r--r--src/main/java/org/traccar/helper/model/GeofenceUtil.java42
-rw-r--r--src/main/java/org/traccar/helper/model/PositionUtil.java80
-rw-r--r--src/main/java/org/traccar/helper/model/UserUtil.java78
18 files changed, 731 insertions, 52 deletions
diff --git a/src/main/java/org/traccar/helper/BitUtil.java b/src/main/java/org/traccar/helper/BitUtil.java
index b6108edff..829ddebc9 100644
--- a/src/main/java/org/traccar/helper/BitUtil.java
+++ b/src/main/java/org/traccar/helper/BitUtil.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 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.
@@ -21,7 +21,7 @@ public final class BitUtil {
}
public static boolean check(long number, int index) {
- return (number & (1 << index)) != 0;
+ return (number & (1L << index)) != 0;
}
public static int between(int number, int from, int to) {
diff --git a/src/main/java/org/traccar/helper/BufferUtil.java b/src/main/java/org/traccar/helper/BufferUtil.java
index 9485c17c6..12c31ba9d 100644
--- a/src/main/java/org/traccar/helper/BufferUtil.java
+++ b/src/main/java/org/traccar/helper/BufferUtil.java
@@ -27,6 +27,24 @@ public final class BufferUtil {
private BufferUtil() {
}
+ public static int readSignedMagnitudeInt(ByteBuf buffer) {
+ long value = buffer.readUnsignedInt();
+ int result = (int) BitUtil.to(value, 31);
+ return BitUtil.check(value, 31) ? -result : result;
+ }
+
+ public static int indexOf(ByteBuf buffer, int fromIndex, int toIndex, byte value, int count) {
+ int startIndex = fromIndex;
+ for (int i = 0; i < count; i++) {
+ int result = buffer.indexOf(startIndex, toIndex, value);
+ if (result < 0 || i == count - 1) {
+ return result;
+ }
+ startIndex = result + 1;
+ }
+ return -1;
+ }
+
public static int indexOf(String needle, ByteBuf haystack) {
return indexOf(needle, haystack, haystack.readerIndex(), haystack.writerIndex());
}
@@ -41,16 +59,28 @@ public final class BufferUtil {
}
public static int indexOf(ByteBuf needle, ByteBuf haystack, int startIndex, int endIndex) {
- ByteBuf wrappedHaystack;
- if (startIndex == haystack.readerIndex() && endIndex == haystack.writerIndex()) {
- wrappedHaystack = haystack;
- } else {
- wrappedHaystack = Unpooled.wrappedBuffer(haystack);
- wrappedHaystack.readerIndex(startIndex - haystack.readerIndex());
- wrappedHaystack.writerIndex(endIndex - haystack.readerIndex());
+ int originalReaderIndex = haystack.readerIndex();
+ int originalWriterIndex = haystack.writerIndex();
+ try {
+ haystack.readerIndex(startIndex);
+ haystack.writerIndex(endIndex);
+ return ByteBufUtil.indexOf(needle, haystack);
+ } finally {
+ haystack.readerIndex(originalReaderIndex);
+ haystack.writerIndex(originalWriterIndex);
+ }
+ }
+
+ public static boolean isPrintable(ByteBuf buf, int length) {
+ boolean printable = true;
+ for (int i = 0; i < length; i++) {
+ byte b = buf.getByte(buf.readerIndex() + i);
+ if (b < 32 && b != '\r' && b != '\n') {
+ printable = false;
+ break;
+ }
}
- int result = ByteBufUtil.indexOf(needle, wrappedHaystack);
- return result < 0 ? result : startIndex + result;
+ return printable;
}
}
diff --git a/src/main/java/org/traccar/helper/Checksum.java b/src/main/java/org/traccar/helper/Checksum.java
index 8c3d0063a..db5817275 100644
--- a/src/main/java/org/traccar/helper/Checksum.java
+++ b/src/main/java/org/traccar/helper/Checksum.java
@@ -200,4 +200,19 @@ public final class Checksum {
return (10 - (checksum % 10)) % 10;
}
+ public static int ip(ByteBuffer data) {
+ int sum = 0;
+ while (data.remaining() > 0) {
+ sum += data.get() & 0xff;
+ if ((sum & 0x80000000) > 0) {
+ sum = (sum & 0xffff) + (sum >> 16);
+ }
+ }
+ while ((sum >> 16) > 0) {
+ sum = (sum & 0xffff) + sum >> 16;
+ }
+ sum = (sum == 0xffff) ? sum & 0xffff : (~sum) & 0xffff;
+ return sum;
+ }
+
}
diff --git a/src/main/java/org/traccar/helper/ClassScanner.java b/src/main/java/org/traccar/helper/ClassScanner.java
index c928f6a12..c201d101f 100644
--- a/src/main/java/org/traccar/helper/ClassScanner.java
+++ b/src/main/java/org/traccar/helper/ClassScanner.java
@@ -46,7 +46,7 @@ public final class ClassScanner {
URL packageUrl = baseClass.getClassLoader().getResource(packagePath);
if (packageUrl.getProtocol().equals("jar")) {
- String jarFileName = URLDecoder.decode(packageUrl.getFile(), StandardCharsets.UTF_8.name());
+ String jarFileName = URLDecoder.decode(packageUrl.getFile(), StandardCharsets.UTF_8);
try (JarFile jf = new JarFile(jarFileName.substring(5, jarFileName.indexOf("!")))) {
Enumeration<JarEntry> jarEntries = jf.entries();
while (jarEntries.hasMoreElements()) {
diff --git a/src/main/java/org/traccar/helper/Log.java b/src/main/java/org/traccar/helper/Log.java
index 8c67f9ddc..9aaf1cfd3 100644
--- a/src/main/java/org/traccar/helper/Log.java
+++ b/src/main/java/org/traccar/helper/Log.java
@@ -28,6 +28,10 @@ import java.io.StringWriter;
import java.io.Writer;
import java.net.URL;
import java.nio.charset.StandardCharsets;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.ConsoleHandler;
@@ -51,10 +55,12 @@ public final class Log {
private String suffix;
private Writer writer;
private final boolean rotate;
+ private final String template;
- RollingFileHandler(String name, boolean rotate) {
+ RollingFileHandler(String name, boolean rotate, String rotateInterval) {
this.name = name;
this.rotate = rotate;
+ this.template = rotateInterval.equalsIgnoreCase("HOUR") ? "yyyyMMddHH" : "yyyyMMdd";
}
@Override
@@ -63,7 +69,7 @@ public final class Log {
try {
String suffix = "";
if (rotate) {
- suffix = new SimpleDateFormat("yyyyMMdd").format(new Date(record.getMillis()));
+ suffix = new SimpleDateFormat(template).format(new Date(record.getMillis()));
if (writer != null && !suffix.equals(this.suffix)) {
writer.close();
writer = null;
@@ -165,7 +171,7 @@ public final class Log {
public static void setupDefaultLogger() {
String path = null;
- URL url = ClassLoader.getSystemClassLoader().getResource(".");
+ URL url = ClassLoader.getSystemClassLoader().getResource(".");
if (url != null) {
File jarPath = new File(url.getPath());
File logsPath = new File(jarPath, "logs");
@@ -174,7 +180,7 @@ public final class Log {
}
path = new File(logsPath, "tracker-server.log").getPath();
}
- setupLogger(path == null, path, Level.WARNING.getName(), false, true);
+ setupLogger(path == null, path, Level.WARNING.getName(), false, true, "DAY");
}
public static void setupLogger(Config config) {
@@ -183,11 +189,13 @@ public final class Log {
config.getString(Keys.LOGGER_FILE),
config.getString(Keys.LOGGER_LEVEL),
config.getBoolean(Keys.LOGGER_FULL_STACK_TRACES),
- config.getBoolean(Keys.LOGGER_ROTATE));
+ config.getBoolean(Keys.LOGGER_ROTATE),
+ config.getString(Keys.LOGGER_ROTATE_INTERVAL));
}
private static void setupLogger(
- boolean console, String file, String levelString, boolean fullStackTraces, boolean rotate) {
+ boolean console, String file, String levelString,
+ boolean fullStackTraces, boolean rotate, String rotateInterval) {
Logger rootLogger = Logger.getLogger("");
for (Handler handler : rootLogger.getHandlers()) {
@@ -198,7 +206,7 @@ public final class Log {
if (console) {
handler = new ConsoleHandler();
} else {
- handler = new RollingFileHandler(file, rotate);
+ handler = new RollingFileHandler(file, rotate, rotateInterval);
}
handler.setFormatter(new LogFormatter(fullStackTraces));
@@ -269,4 +277,18 @@ public final class Log {
return s.toString();
}
+ public static long[] getStorageSpace() {
+ long usable = 0;
+ long total = 0;
+ for (Path root : FileSystems.getDefault().getRootDirectories()) {
+ try {
+ FileStore store = Files.getFileStore(root);
+ usable += store.getUsableSpace();
+ total += store.getTotalSpace();
+ } catch (IOException ignored) {
+ }
+ }
+ return new long[]{usable, total};
+ }
+
}
diff --git a/src/main/java/org/traccar/helper/LogAction.java b/src/main/java/org/traccar/helper/LogAction.java
index d16b25483..b255b9206 100644
--- a/src/main/java/org/traccar/helper/LogAction.java
+++ b/src/main/java/org/traccar/helper/LogAction.java
@@ -47,7 +47,7 @@ public final class LogAction {
private static final String PATTERN_OBJECT = "user: %d, action: %s, object: %s, id: %d";
private static final String PATTERN_LINK = "user: %d, action: %s, owner: %s, id: %d, property: %s, id: %d";
- private static final String PATTERN_LOGIN = "user: %d, action: %s";
+ private static final String PATTERN_LOGIN = "user: %d, action: %s, from: %s";
private static final String PATTERN_LOGIN_FAILED = "login failed from: %s";
private static final String PATTERN_DEVICE_ACCUMULATORS = "user: %d, action: %s, deviceId: %d";
private static final String PATTERN_REPORT = "user: %d, report: %s, from: %s, to: %s, devices: %s, groups: %s";
@@ -72,12 +72,12 @@ public final class LogAction {
logLinkAction(ACTION_UNLINK, userId, owner, ownerId, property, propertyId);
}
- public static void login(long userId) {
- logLoginAction(ACTION_LOGIN, userId);
+ public static void login(long userId, String remoteAddress) {
+ logLoginAction(ACTION_LOGIN, userId, remoteAddress);
}
- public static void logout(long userId) {
- logLoginAction(ACTION_LOGOUT, userId);
+ public static void logout(long userId, String remoteAddress) {
+ logLoginAction(ACTION_LOGOUT, userId, remoteAddress);
}
public static void failedLogin(String remoteAddress) {
@@ -105,8 +105,11 @@ public final class LogAction {
Introspector.decapitalize(property.getSimpleName()), propertyId));
}
- private static void logLoginAction(String action, long userId) {
- LOGGER.info(String.format(PATTERN_LOGIN, userId, action));
+ private static void logLoginAction(String action, long userId, String remoteAddress) {
+ if (remoteAddress == null || remoteAddress.isEmpty()) {
+ remoteAddress = "unknown";
+ }
+ LOGGER.info(String.format(PATTERN_LOGIN, userId, action, remoteAddress));
}
public static void logReport(
diff --git a/src/main/java/org/traccar/helper/NetworkUtil.java b/src/main/java/org/traccar/helper/NetworkUtil.java
new file mode 100644
index 000000000..2fe3487da
--- /dev/null
+++ b/src/main/java/org/traccar/helper/NetworkUtil.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+import io.netty.channel.Channel;
+import io.netty.channel.socket.DatagramChannel;
+
+public final class NetworkUtil {
+
+ private NetworkUtil() {
+ }
+
+ public static String session(Channel channel) {
+ char transport = channel instanceof DatagramChannel ? 'U' : 'T';
+ return transport + channel.id().asShortText();
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/ObdDecoder.java b/src/main/java/org/traccar/helper/ObdDecoder.java
index b22065f4e..3cbae334a 100644
--- a/src/main/java/org/traccar/helper/ObdDecoder.java
+++ b/src/main/java/org/traccar/helper/ObdDecoder.java
@@ -51,22 +51,7 @@ public final class ObdDecoder {
StringBuilder codes = new StringBuilder();
for (int i = 0; i < value.length() / 4; i++) {
int numValue = Integer.parseInt(value.substring(i * 4, (i + 1) * 4), 16);
- codes.append(' ');
- switch (numValue >> 14) {
- case 1:
- codes.append('C');
- break;
- case 2:
- codes.append('B');
- break;
- case 3:
- codes.append('U');
- break;
- default:
- codes.append('P');
- break;
- }
- codes.append(String.format("%04X", numValue & 0x3FFF));
+ codes.append(' ').append(decodeCode(numValue));
}
if (codes.length() > 0) {
return createEntry(Position.KEY_DTCS, codes.toString().trim());
@@ -75,6 +60,25 @@ public final class ObdDecoder {
}
}
+ public static String decodeCode(int value) {
+ char prefix;
+ switch (value >> 14) {
+ case 1:
+ prefix = 'C';
+ break;
+ case 2:
+ prefix = 'B';
+ break;
+ case 3:
+ prefix = 'U';
+ break;
+ default:
+ prefix = 'P';
+ break;
+ }
+ return String.format("%c%04X", prefix, value & 0x3FFF);
+ }
+
public static Map.Entry<String, Object> decodeData(int pid, long value, boolean convert) {
switch (pid) {
case 0x04:
diff --git a/src/main/java/org/traccar/helper/ObjectMapperContextResolver.java b/src/main/java/org/traccar/helper/ObjectMapperContextResolver.java
new file mode 100644
index 000000000..634950b85
--- /dev/null
+++ b/src/main/java/org/traccar/helper/ObjectMapperContextResolver.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.ext.ContextResolver;
+
+// This does not work as a lambda
+public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
+
+ private final ObjectMapper objectMapper;
+
+ @Inject
+ public ObjectMapperContextResolver(ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ }
+
+ @Override
+ public ObjectMapper getContext(Class<?> clazz) {
+ return objectMapper;
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/Parser.java b/src/main/java/org/traccar/helper/Parser.java
index 75106e2ba..c2aea28fa 100644
--- a/src/main/java/org/traccar/helper/Parser.java
+++ b/src/main/java/org/traccar/helper/Parser.java
@@ -48,13 +48,25 @@ public class Parser {
}
public boolean hasNext(int number) {
- String value = matcher.group(position);
- if (value != null && !value.isEmpty()) {
- return true;
- } else {
- position += number;
- return false;
+ for (int i = position; i < position + number; i++) {
+ String value = matcher.group(i);
+ if (value == null || value.isEmpty()) {
+ position += number;
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean hasNextAny(int number) {
+ for (int i = position; i < position + number; i++) {
+ String value = matcher.group(i);
+ if (value != null && !value.isEmpty()) {
+ return true;
+ }
}
+ position += number;
+ return false;
}
public String next() {
@@ -155,6 +167,7 @@ public class Parser {
public enum CoordinateFormat {
DEG_DEG,
+ DEG_DEG_HEM,
DEG_HEM,
DEG_MIN_MIN,
DEG_MIN_HEM,
@@ -173,6 +186,10 @@ public class Parser {
case DEG_DEG:
coordinate = Double.parseDouble(next() + '.' + next());
break;
+ case DEG_DEG_HEM:
+ coordinate = Double.parseDouble(next() + '.' + next());
+ hemisphere = next();
+ break;
case DEG_HEM:
coordinate = nextDouble(0);
hemisphere = next();
diff --git a/src/main/java/org/traccar/helper/PatternUtil.java b/src/main/java/org/traccar/helper/PatternUtil.java
index 74813e1d9..a46c7b7b4 100644
--- a/src/main/java/org/traccar/helper/PatternUtil.java
+++ b/src/main/java/org/traccar/helper/PatternUtil.java
@@ -63,7 +63,7 @@ public final class PatternUtil {
for (int i = 0; i < pattern.length(); i++) {
try {
- Matcher matcher = Pattern.compile("(" + pattern.substring(0, i) + ").*").matcher(input);
+ Matcher matcher = Pattern.compile("(" + pattern.substring(0, i) + ")[\\s\\S]*").matcher(input);
if (matcher.matches()) {
result.patternMatch = pattern.substring(0, i);
result.patternTail = pattern.substring(i);
diff --git a/src/main/java/org/traccar/helper/StringUtil.java b/src/main/java/org/traccar/helper/StringUtil.java
new file mode 100644
index 000000000..9b4d717f4
--- /dev/null
+++ b/src/main/java/org/traccar/helper/StringUtil.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2023 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;
+
+public final class StringUtil {
+
+ private StringUtil() {
+ }
+
+ public static boolean containsHex(String value) {
+ for (char c : value.toCharArray()) {
+ if (c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F') {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/ServletHelper.java b/src/main/java/org/traccar/helper/WebHelper.java
index b6c587ec3..9533fe84b 100644
--- a/src/main/java/org/traccar/helper/ServletHelper.java
+++ b/src/main/java/org/traccar/helper/WebHelper.java
@@ -15,11 +15,18 @@
*/
package org.traccar.helper;
-import javax.servlet.http.HttpServletRequest;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
-public final class ServletHelper {
+import jakarta.servlet.http.HttpServletRequest;
- private ServletHelper() {
+import org.eclipse.jetty.util.URIUtil;
+import org.traccar.config.Config;
+import org.traccar.config.Keys;
+
+public final class WebHelper {
+
+ private WebHelper() {
}
public static String retrieveRemoteAddress(HttpServletRequest request) {
@@ -42,4 +49,17 @@ public final class ServletHelper {
}
}
+ public static String retrieveWebUrl(Config config) {
+ if (config.hasKey(Keys.WEB_URL)) {
+ return config.getString(Keys.WEB_URL).replaceAll("/$", "");
+ } else {
+ String address;
+ try {
+ address = config.getString(Keys.WEB_ADDRESS, InetAddress.getLocalHost().getHostAddress());
+ } catch (UnknownHostException e) {
+ address = "localhost";
+ }
+ return URIUtil.newURI("http", address, config.getInteger(Keys.WEB_PORT), "", "");
+ }
+ }
}
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..2630f64f0
--- /dev/null
+++ b/src/main/java/org/traccar/helper/model/AttributeUtil.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2022 - 2023 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.api.security.PermissionsService;
+import org.traccar.config.Config;
+import org.traccar.config.ConfigKey;
+import org.traccar.config.KeyType;
+import org.traccar.config.Keys;
+import org.traccar.model.Device;
+import org.traccar.model.Group;
+import org.traccar.model.Server;
+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.Request;
+
+public final class AttributeUtil {
+
+ private AttributeUtil() {
+ }
+
+ public interface Provider {
+ Device getDevice();
+ Group getGroup(long groupId);
+ Server getServer();
+ Config getConfig();
+ }
+
+ public static <T> T lookup(CacheManager cacheManager, ConfigKey<T> key, long deviceId) {
+ return lookup(new CacheProvider(cacheManager, deviceId), key);
+ }
+
+ @SuppressWarnings({ "deprecation", "unchecked" })
+ public static <T> T lookup(Provider provider, ConfigKey<T> key) {
+ Device device = provider.getDevice();
+ Object result = device.getAttributes().get(key.getKey());
+ long groupId = device.getGroupId();
+ while (result == null && groupId > 0) {
+ Group group = provider.getGroup(groupId);
+ if (group != null) {
+ result = group.getAttributes().get(key.getKey());
+ groupId = group.getGroupId();
+ } else {
+ groupId = 0;
+ }
+ }
+ if (result == null && key.hasType(KeyType.SERVER)) {
+ result = provider.getServer().getAttributes().get(key.getKey());
+ }
+ if (result == null && key.hasType(KeyType.CONFIG)) {
+ result = provider.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();
+ }
+
+ public static String getDevicePassword(
+ CacheManager cacheManager, long deviceId, String protocol, String defaultPassword) {
+
+ String password = lookup(cacheManager, Keys.DEVICE_PASSWORD, deviceId);
+ if (password != null) {
+ return password;
+ }
+
+ if (protocol != null) {
+ password = cacheManager.getConfig().getString(Keys.PROTOCOL_DEVICE_PASSWORD.withPrefix(protocol));
+ if (password != null) {
+ return password;
+ }
+ }
+
+ return defaultPassword;
+ }
+
+ public static class CacheProvider implements Provider {
+
+ private final CacheManager cacheManager;
+ private final long deviceId;
+
+ public CacheProvider(CacheManager cacheManager, long deviceId) {
+ this.cacheManager = cacheManager;
+ this.deviceId = deviceId;
+ }
+
+ @Override
+ public Device getDevice() {
+ return cacheManager.getObject(Device.class, deviceId);
+ }
+
+ @Override
+ public Group getGroup(long groupId) {
+ return cacheManager.getObject(Group.class, groupId);
+ }
+
+ @Override
+ public Server getServer() {
+ return cacheManager.getServer();
+ }
+
+ @Override
+ public Config getConfig() {
+ return cacheManager.getConfig();
+ }
+ }
+
+ public static class StorageProvider implements Provider {
+
+ private final Config config;
+ private final Storage storage;
+ private final PermissionsService permissionsService;
+ private final Device device;
+
+ public StorageProvider(Config config, Storage storage, PermissionsService permissionsService, Device device) {
+ this.config = config;
+ this.storage = storage;
+ this.permissionsService = permissionsService;
+ this.device = device;
+ }
+
+ @Override
+ public Device getDevice() {
+ return device;
+ }
+
+ @Override
+ public Group getGroup(long groupId) {
+ try {
+ return storage.getObject(
+ Group.class, new Request(new Columns.All(), new Condition.Equals("id", groupId)));
+ } catch (StorageException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Server getServer() {
+ try {
+ return permissionsService.getServer();
+ } catch (StorageException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Config getConfig() {
+ return config;
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/model/DeviceUtil.java b/src/main/java/org/traccar/helper/model/DeviceUtil.java
new file mode 100644
index 000000000..5d8cb5f25
--- /dev/null
+++ b/src/main/java/org/traccar/helper/model/DeviceUtil.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2022 - 2023 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.model.Device;
+import org.traccar.model.Group;
+import org.traccar.model.User;
+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.Request;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public final class DeviceUtil {
+
+ private DeviceUtil() {
+ }
+
+ public static void resetStatus(Storage storage) throws StorageException {
+ storage.updateObject(new Device(), new Request(new Columns.Include("status")));
+ }
+
+
+ public static Collection<Device> getAccessibleDevices(
+ Storage storage, long userId,
+ Collection<Long> deviceIds, Collection<Long> groupIds) throws StorageException {
+
+ var devices = storage.getObjects(Device.class, new Request(
+ new Columns.All(),
+ new Condition.Permission(User.class, userId, Device.class)));
+ var deviceById = devices.stream()
+ .collect(Collectors.toUnmodifiableMap(Device::getId, x -> x));
+ var devicesByGroup = devices.stream()
+ .filter(x -> x.getGroupId() > 0)
+ .collect(Collectors.groupingBy(Device::getGroupId));
+
+ var groups = storage.getObjects(Group.class, new Request(
+ new Columns.All(),
+ new Condition.Permission(User.class, userId, Group.class)));
+ var groupsByGroup = groups.stream()
+ .filter(x -> x.getGroupId() > 0)
+ .collect(Collectors.groupingBy(Group::getGroupId));
+
+ var results = deviceIds.stream()
+ .map(deviceById::get)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+
+ var groupQueue = new LinkedList<>(groupIds);
+ while (!groupQueue.isEmpty()) {
+ long groupId = groupQueue.pop();
+ results.addAll(devicesByGroup.getOrDefault(groupId, Collections.emptyList()));
+ groupQueue.addAll(groupsByGroup.getOrDefault(groupId, Collections.emptyList())
+ .stream().map(Group::getId).collect(Collectors.toUnmodifiableList()));
+ }
+
+ return results;
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/model/GeofenceUtil.java b/src/main/java/org/traccar/helper/model/GeofenceUtil.java
new file mode 100644
index 000000000..9f063a8b4
--- /dev/null
+++ b/src/main/java/org/traccar/helper/model/GeofenceUtil.java
@@ -0,0 +1,42 @@
+/*
+ * 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.Config;
+import org.traccar.model.Geofence;
+import org.traccar.model.Position;
+import org.traccar.session.cache.CacheManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class GeofenceUtil {
+
+ private GeofenceUtil() {
+ }
+
+ public static List<Long> getCurrentGeofences(Config config, CacheManager cacheManager, Position position) {
+ List<Long> result = new ArrayList<>();
+ for (Geofence geofence : cacheManager.getDeviceObjects(position.getDeviceId(), Geofence.class)) {
+ if (geofence.getGeometry().containsPoint(
+ config, geofence, position.getLatitude(), position.getLongitude())) {
+ result.add(geofence.getId());
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/model/PositionUtil.java b/src/main/java/org/traccar/helper/model/PositionUtil.java
new file mode 100644
index 000000000..6c380b81a
--- /dev/null
+++ b/src/main/java/org/traccar/helper/model/PositionUtil.java
@@ -0,0 +1,80 @@
+/*
+ * 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.model.BaseModel;
+import org.traccar.model.Device;
+import org.traccar.model.Position;
+import org.traccar.model.User;
+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.Order;
+import org.traccar.storage.query.Request;
+
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public final class PositionUtil {
+
+ private PositionUtil() {
+ }
+
+ public static boolean isLatest(CacheManager cacheManager, Position position) {
+ Position lastPosition = cacheManager.getPosition(position.getDeviceId());
+ return lastPosition == null || position.getFixTime().compareTo(lastPosition.getFixTime()) >= 0;
+ }
+
+ public static double calculateDistance(Position first, Position last, boolean useOdometer) {
+ double distance;
+ double firstOdometer = first.getDouble(Position.KEY_ODOMETER);
+ double lastOdometer = last.getDouble(Position.KEY_ODOMETER);
+
+ if (useOdometer && firstOdometer != 0.0 && lastOdometer != 0.0) {
+ distance = lastOdometer - firstOdometer;
+ } else {
+ distance = last.getDouble(Position.KEY_TOTAL_DISTANCE) - first.getDouble(Position.KEY_TOTAL_DISTANCE);
+ }
+ return distance;
+ }
+
+ public static List<Position> getPositions(
+ Storage storage, long deviceId, Date from, Date to) throws StorageException {
+ return storage.getObjects(Position.class, new Request(
+ new Columns.All(),
+ new Condition.And(
+ new Condition.Equals("deviceId", deviceId),
+ new Condition.Between("fixTime", "from", from, "to", to)),
+ new Order("fixTime")));
+ }
+
+ public static List<Position> getLatestPositions(Storage storage, long userId) throws StorageException {
+ var devices = storage.getObjects(Device.class, new Request(
+ new Columns.Include("id"),
+ new Condition.Permission(User.class, userId, Device.class)));
+ var deviceIds = devices.stream().map(BaseModel::getId).collect(Collectors.toUnmodifiableSet());
+
+ var positions = storage.getObjects(Position.class, new Request(
+ new Columns.All(), new Condition.LatestPositions()));
+ return positions.stream()
+ .filter(position -> deviceIds.contains(position.getDeviceId()))
+ .collect(Collectors.toList());
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/model/UserUtil.java b/src/main/java/org/traccar/helper/model/UserUtil.java
new file mode 100644
index 000000000..4b1c404f9
--- /dev/null
+++ b/src/main/java/org/traccar/helper/model/UserUtil.java
@@ -0,0 +1,78 @@
+/*
+ * 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.Config;
+import org.traccar.config.Keys;
+import org.traccar.model.Server;
+import org.traccar.model.User;
+import org.traccar.storage.Storage;
+import org.traccar.storage.StorageException;
+import org.traccar.storage.query.Columns;
+import org.traccar.storage.query.Order;
+import org.traccar.storage.query.Request;
+
+import java.util.Date;
+import java.util.TimeZone;
+
+public final class UserUtil {
+
+ private UserUtil() {
+ }
+
+ public static boolean isEmpty(Storage storage) throws StorageException {
+ return storage.getObjects(User.class, new Request(
+ new Columns.Include("id"),
+ new Order("id", false, 1))).isEmpty();
+ }
+
+ public static String getDistanceUnit(Server server, User user) {
+ return lookupStringAttribute(server, user, "distanceUnit", "km");
+ }
+
+ public static String getSpeedUnit(Server server, User user) {
+ return lookupStringAttribute(server, user, "speedUnit", "kn");
+ }
+
+ public static String getVolumeUnit(Server server, User user) {
+ return lookupStringAttribute(server, user, "volumeUnit", "ltr");
+ }
+
+ public static TimeZone getTimezone(Server server, User user) {
+ String timezone = lookupStringAttribute(server, user, "timezone", null);
+ return timezone != null ? TimeZone.getTimeZone(timezone) : TimeZone.getDefault();
+ }
+
+ private static String lookupStringAttribute(Server server, User user, String key, String defaultValue) {
+ String preference;
+ String serverPreference = server.getString(key);
+ String userPreference = user.getString(key);
+ if (server.getForceSettings()) {
+ preference = serverPreference != null ? serverPreference : userPreference;
+ } else {
+ preference = userPreference != null ? userPreference : serverPreference;
+ }
+ return preference != null ? preference : defaultValue;
+ }
+
+ public static void setUserDefaults(User user, Config config) {
+ user.setDeviceLimit(config.getInteger(Keys.USERS_DEFAULT_DEVICE_LIMIT));
+ int expirationDays = config.getInteger(Keys.USERS_DEFAULT_EXPIRATION_DAYS);
+ if (expirationDays > 0) {
+ user.setExpirationTime(new Date(System.currentTimeMillis() + expirationDays * 86400000L));
+ }
+ }
+}