aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/traccar/session
diff options
context:
space:
mode:
authorIván Ávalos <avalos@disroot.org>2024-03-07 22:59:12 -0600
committerIván Ávalos <avalos@disroot.org>2024-03-07 22:59:12 -0600
commit00d3ddf7b5e335a5112a28a547c4c9ec2c3bd1d2 (patch)
treea02ecb75a8533194314d32c0c667520bff5e919d /src/main/java/org/traccar/session
parent8e1bc703227a875c20e453704d774ce5772f2621 (diff)
parent75b404db5c790bf37c05fabf1cbbd2027ad1db25 (diff)
downloadtrackermap-server-00d3ddf7b5e335a5112a28a547c4c9ec2c3bd1d2.tar.gz
trackermap-server-00d3ddf7b5e335a5112a28a547c4c9ec2c3bd1d2.tar.bz2
trackermap-server-00d3ddf7b5e335a5112a28a547c4c9ec2c3bd1d2.zip
Merge branch 'master' of https://github.com/traccar/traccar
Diffstat (limited to 'src/main/java/org/traccar/session')
-rw-r--r--src/main/java/org/traccar/session/ConnectionManager.java86
-rw-r--r--src/main/java/org/traccar/session/DeviceSession.java9
-rw-r--r--src/main/java/org/traccar/session/Endpoint.java58
-rw-r--r--src/main/java/org/traccar/session/cache/CacheGraph.java139
-rw-r--r--src/main/java/org/traccar/session/cache/CacheKey.java4
-rw-r--r--src/main/java/org/traccar/session/cache/CacheManager.java390
-rw-r--r--src/main/java/org/traccar/session/cache/CacheNode.java40
-rw-r--r--src/main/java/org/traccar/session/cache/CacheValue.java53
-rw-r--r--src/main/java/org/traccar/session/cache/WeakValueMap.java44
-rw-r--r--src/main/java/org/traccar/session/state/OverspeedProcessor.java44
10 files changed, 466 insertions, 401 deletions
diff --git a/src/main/java/org/traccar/session/ConnectionManager.java b/src/main/java/org/traccar/session/ConnectionManager.java
index 37a42d827..42dcf5ce9 100644
--- a/src/main/java/org/traccar/session/ConnectionManager.java
+++ b/src/main/java/org/traccar/session/ConnectionManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2024 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.
@@ -30,6 +30,7 @@ import org.traccar.database.NotificationManager;
import org.traccar.model.BaseModel;
import org.traccar.model.Device;
import org.traccar.model.Event;
+import org.traccar.model.LogRecord;
import org.traccar.model.Position;
import org.traccar.model.User;
import org.traccar.session.cache.CacheManager;
@@ -39,8 +40,8 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Arrays;
@@ -62,9 +63,11 @@ public class ConnectionManager implements BroadcastInterface {
private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionManager.class);
private final long deviceTimeout;
+ private final boolean showUnknownDevices;
private final Map<Long, DeviceSession> sessionsByDeviceId = new ConcurrentHashMap<>();
- private final Map<Endpoint, Map<String, DeviceSession>> sessionsByEndpoint = new ConcurrentHashMap<>();
+ private final Map<SocketAddress, Map<String, DeviceSession>> sessionsByEndpoint = new ConcurrentHashMap<>();
+ private final Map<SocketAddress, String> unknownByEndpoint = new ConcurrentHashMap<>();
private final Config config;
private final CacheManager cacheManager;
@@ -93,6 +96,7 @@ public class ConnectionManager implements BroadcastInterface {
this.broadcastService = broadcastService;
this.deviceLookupService = deviceLookupService;
deviceTimeout = config.getLong(Keys.STATUS_TIMEOUT);
+ showUnknownDevices = config.getBoolean(Keys.WEB_SHOW_UNKNOWN_DEVICES);
broadcastService.registerListener(this);
}
@@ -102,11 +106,10 @@ public class ConnectionManager implements BroadcastInterface {
public DeviceSession getDeviceSession(
Protocol protocol, Channel channel, SocketAddress remoteAddress,
- String... uniqueIds) throws StorageException {
+ String... uniqueIds) throws Exception {
- Endpoint endpoint = new Endpoint(channel, remoteAddress);
Map<String, DeviceSession> endpointSessions = sessionsByEndpoint.getOrDefault(
- endpoint, new ConcurrentHashMap<>());
+ remoteAddress, new ConcurrentHashMap<>());
uniqueIds = Arrays.stream(uniqueIds).filter(Objects::nonNull).toArray(String[]::new);
if (uniqueIds.length > 0) {
@@ -122,28 +125,31 @@ public class ConnectionManager implements BroadcastInterface {
Device device = deviceLookupService.lookup(uniqueIds);
+ String firstUniqueId = uniqueIds[0];
if (device == null && config.getBoolean(Keys.DATABASE_REGISTER_UNKNOWN)) {
- device = addUnknownDevice(uniqueIds[0]);
+ if (firstUniqueId.matches(config.getString(Keys.DATABASE_REGISTER_UNKNOWN_REGEX))) {
+ device = addUnknownDevice(firstUniqueId);
+ }
}
if (device != null) {
+ unknownByEndpoint.remove(remoteAddress);
device.checkDisabled();
DeviceSession oldSession = sessionsByDeviceId.remove(device.getId());
if (oldSession != null) {
- Endpoint oldEndpoint = new Endpoint(oldSession.getChannel(), oldSession.getRemoteAddress());
- Map<String, DeviceSession> oldEndpointSessions = sessionsByEndpoint.get(oldEndpoint);
+ Map<String, DeviceSession> oldEndpointSessions = sessionsByEndpoint.get(oldSession.getRemoteAddress());
if (oldEndpointSessions != null && oldEndpointSessions.size() > 1) {
oldEndpointSessions.remove(device.getUniqueId());
} else {
- sessionsByEndpoint.remove(oldEndpoint);
+ sessionsByEndpoint.remove(oldSession.getRemoteAddress());
}
}
DeviceSession deviceSession = new DeviceSession(
- device.getId(), device.getUniqueId(), protocol, channel, remoteAddress);
+ device.getId(), device.getUniqueId(), device.getModel(), protocol, channel, remoteAddress);
endpointSessions.put(device.getUniqueId(), deviceSession);
- sessionsByEndpoint.put(endpoint, endpointSessions);
+ sessionsByEndpoint.put(remoteAddress, endpointSessions);
sessionsByDeviceId.put(device.getId(), deviceSession);
if (oldSession == null) {
@@ -152,6 +158,7 @@ public class ConnectionManager implements BroadcastInterface {
return deviceSession;
} else {
+ unknownByEndpoint.put(remoteAddress, firstUniqueId);
LOGGER.warn("Unknown device - " + String.join(" ", uniqueIds)
+ " (" + ((InetSocketAddress) remoteAddress).getHostString() + ")");
return null;
@@ -180,16 +187,19 @@ public class ConnectionManager implements BroadcastInterface {
}
public void deviceDisconnected(Channel channel, boolean supportsOffline) {
- Endpoint endpoint = new Endpoint(channel, channel.remoteAddress());
- Map<String, DeviceSession> endpointSessions = sessionsByEndpoint.remove(endpoint);
- if (endpointSessions != null) {
- for (DeviceSession deviceSession : endpointSessions.values()) {
- if (supportsOffline) {
- updateDevice(deviceSession.getDeviceId(), Device.STATUS_OFFLINE, null);
+ SocketAddress remoteAddress = channel.remoteAddress();
+ if (remoteAddress != null) {
+ Map<String, DeviceSession> endpointSessions = sessionsByEndpoint.remove(remoteAddress);
+ if (endpointSessions != null) {
+ for (DeviceSession deviceSession : endpointSessions.values()) {
+ if (supportsOffline) {
+ updateDevice(deviceSession.getDeviceId(), Device.STATUS_OFFLINE, null);
+ }
+ sessionsByDeviceId.remove(deviceSession.getDeviceId());
+ cacheManager.removeDevice(deviceSession.getDeviceId());
}
- sessionsByDeviceId.remove(deviceSession.getDeviceId());
- cacheManager.removeDevice(deviceSession.getDeviceId());
}
+ unknownByEndpoint.remove(remoteAddress);
}
}
@@ -202,8 +212,7 @@ public class ConnectionManager implements BroadcastInterface {
DeviceSession deviceSession = sessionsByDeviceId.remove(deviceId);
if (deviceSession != null) {
cacheManager.removeDevice(deviceId);
- Endpoint endpoint = new Endpoint(deviceSession.getChannel(), deviceSession.getRemoteAddress());
- sessionsByEndpoint.computeIfPresent(endpoint, (e, sessions) -> {
+ sessionsByEndpoint.computeIfPresent(deviceSession.getRemoteAddress(), (e, sessions) -> {
sessions.remove(deviceSession.getUniqueId());
return sessions.isEmpty() ? null : sessions;
});
@@ -325,11 +334,9 @@ public class ConnectionManager implements BroadcastInterface {
}
@Override
- public synchronized void invalidatePermission(
- boolean local,
- Class<? extends BaseModel> clazz1, long id1,
- Class<? extends BaseModel> clazz2, long id2) {
- if (clazz1.equals(User.class) && clazz2.equals(Device.class)) {
+ public synchronized <T1 extends BaseModel, T2 extends BaseModel> void invalidatePermission(
+ boolean local, Class<T1> clazz1, long id1, Class<T2> clazz2, long id2, boolean link) {
+ if (link && clazz1.equals(User.class) && clazz2.equals(Device.class)) {
if (listeners.containsKey(id1)) {
userDevices.get(id1).add(id2);
deviceUsers.put(id2, new HashSet<>(List.of(id1)));
@@ -337,11 +344,34 @@ public class ConnectionManager implements BroadcastInterface {
}
}
+ public synchronized void updateLog(LogRecord record) {
+ var sessions = sessionsByEndpoint.getOrDefault(record.getAddress(), Map.of());
+ if (sessions.isEmpty()) {
+ String unknownUniqueId = unknownByEndpoint.get(record.getAddress());
+ if (unknownUniqueId != null && showUnknownDevices) {
+ record.setUniqueId(unknownUniqueId);
+ listeners.values().stream()
+ .flatMap(Set::stream)
+ .forEach((listener) -> listener.onUpdateLog(record));
+ }
+ } else {
+ var firstEntry = sessions.entrySet().iterator().next();
+ record.setUniqueId(firstEntry.getKey());
+ record.setDeviceId(firstEntry.getValue().getDeviceId());
+ for (long userId : deviceUsers.getOrDefault(record.getDeviceId(), Set.of())) {
+ for (UpdateListener listener : listeners.getOrDefault(userId, Set.of())) {
+ listener.onUpdateLog(record);
+ }
+ }
+ }
+ }
+
public interface UpdateListener {
void onKeepalive();
void onUpdateDevice(Device device);
void onUpdatePosition(Position position);
void onUpdateEvent(Event event);
+ void onUpdateLog(LogRecord record);
}
public synchronized void addListener(long userId, UpdateListener listener) throws StorageException {
diff --git a/src/main/java/org/traccar/session/DeviceSession.java b/src/main/java/org/traccar/session/DeviceSession.java
index 009f90f5a..f124ca7f9 100644
--- a/src/main/java/org/traccar/session/DeviceSession.java
+++ b/src/main/java/org/traccar/session/DeviceSession.java
@@ -29,14 +29,17 @@ public class DeviceSession {
private final long deviceId;
private final String uniqueId;
+ private final String model;
private final Protocol protocol;
private final Channel channel;
private final SocketAddress remoteAddress;
public DeviceSession(
- long deviceId, String uniqueId, Protocol protocol, Channel channel, SocketAddress remoteAddress) {
+ long deviceId, String uniqueId, String model,
+ Protocol protocol, Channel channel, SocketAddress remoteAddress) {
this.deviceId = deviceId;
this.uniqueId = uniqueId;
+ this.model = model;
this.protocol = protocol;
this.channel = channel;
this.remoteAddress = remoteAddress;
@@ -50,6 +53,10 @@ public class DeviceSession {
return uniqueId;
}
+ public String getModel() {
+ return model;
+ }
+
public Channel getChannel() {
return channel;
}
diff --git a/src/main/java/org/traccar/session/Endpoint.java b/src/main/java/org/traccar/session/Endpoint.java
deleted file mode 100644
index 76aac3444..000000000
--- a/src/main/java/org/traccar/session/Endpoint.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.session;
-
-import io.netty.channel.Channel;
-
-import java.net.SocketAddress;
-import java.util.Objects;
-
-public class Endpoint {
-
- private final Channel channel;
- private final SocketAddress remoteAddress;
-
- public Endpoint(Channel channel, SocketAddress remoteAddress) {
- this.channel = channel;
- this.remoteAddress = remoteAddress;
- }
-
- public Channel getChannel() {
- return channel;
- }
-
- public SocketAddress getRemoteAddress() {
- return remoteAddress;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- Endpoint endpoint = (Endpoint) o;
- return channel.equals(endpoint.channel) && remoteAddress.equals(endpoint.remoteAddress);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(channel, remoteAddress);
- }
-
-}
diff --git a/src/main/java/org/traccar/session/cache/CacheGraph.java b/src/main/java/org/traccar/session/cache/CacheGraph.java
new file mode 100644
index 000000000..c99997288
--- /dev/null
+++ b/src/main/java/org/traccar/session/cache/CacheGraph.java
@@ -0,0 +1,139 @@
+/*
+ * 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.session.cache;
+
+import org.traccar.model.BaseModel;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+public class CacheGraph {
+
+ private final Map<CacheKey, CacheNode> roots = new HashMap<>();
+ private final WeakValueMap<CacheKey, CacheNode> nodes = new WeakValueMap<>();
+
+ void addObject(BaseModel value) {
+ CacheKey key = new CacheKey(value);
+ CacheNode node = new CacheNode(value);
+ roots.put(key, node);
+ nodes.put(key, node);
+ }
+
+ void removeObject(Class<? extends BaseModel> clazz, long id) {
+ CacheKey key = new CacheKey(clazz, id);
+ CacheNode node = nodes.remove(key);
+ if (node != null) {
+ node.getAllLinks(false).forEach(child -> child.getLinks(key.getClazz(), true).remove(node));
+ }
+ roots.remove(key);
+ }
+
+ @SuppressWarnings("unchecked")
+ <T extends BaseModel> T getObject(Class<T> clazz, long id) {
+ CacheNode node = nodes.get(new CacheKey(clazz, id));
+ return node != null ? (T) node.getValue() : null;
+ }
+
+ <T extends BaseModel> Stream<T> getObjects(
+ Class<? extends BaseModel> fromClass, long fromId,
+ Class<T> clazz, Set<Class<? extends BaseModel>> proxies, boolean forward) {
+
+ CacheNode rootNode = nodes.get(new CacheKey(fromClass, fromId));
+ if (rootNode != null) {
+ return getObjectStream(rootNode, clazz, proxies, forward);
+ } else {
+ return Stream.empty();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T extends BaseModel> Stream<T> getObjectStream(
+ CacheNode rootNode, Class<T> clazz, Set<Class<? extends BaseModel>> proxies, boolean forward) {
+
+ if (proxies.contains(clazz)) {
+ return Stream.empty();
+ }
+
+ var directSteam = rootNode.getLinks(clazz, forward).stream()
+ .map(node -> (T) node.getValue());
+
+ var proxyStream = proxies.stream()
+ .flatMap(proxyClass -> rootNode.getLinks(proxyClass, forward).stream()
+ .flatMap(node -> getObjectStream(node, clazz, proxies, forward)));
+
+ return Stream.concat(directSteam, proxyStream);
+ }
+
+ void updateObject(BaseModel value) {
+ CacheNode node = nodes.get(new CacheKey(value));
+ if (node != null) {
+ node.setValue(value);
+ }
+ }
+
+ boolean addLink(
+ Class<? extends BaseModel> fromClazz, long fromId,
+ BaseModel toValue) {
+ boolean stop = true;
+ CacheNode fromNode = nodes.get(new CacheKey(fromClazz, fromId));
+ if (fromNode != null) {
+ CacheKey toKey = new CacheKey(toValue);
+ CacheNode toNode = nodes.get(toKey);
+ if (toNode == null) {
+ stop = false;
+ toNode = new CacheNode(toValue);
+ nodes.put(toKey, toNode);
+ }
+ fromNode.getLinks(toValue.getClass(), true).add(toNode);
+ toNode.getLinks(fromClazz, false).add(fromNode);
+ }
+ return stop;
+ }
+
+ void removeLink(
+ Class<? extends BaseModel> fromClazz, long fromId,
+ Class<? extends BaseModel> toClazz, long toId) {
+ CacheNode fromNode = nodes.get(new CacheKey(fromClazz, fromId));
+ if (fromNode != null) {
+ CacheNode toNode = nodes.get(new CacheKey(toClazz, toId));
+ if (toNode != null) {
+ fromNode.getLinks(toClazz, true).remove(toNode);
+ toNode.getLinks(fromClazz, false).remove(fromNode);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (CacheNode node : roots.values()) {
+ printNode(stringBuilder, node, "");
+ }
+ return stringBuilder.toString().trim();
+ }
+
+ private void printNode(StringBuilder stringBuilder, CacheNode node, String indentation) {
+ stringBuilder
+ .append('\n')
+ .append(indentation)
+ .append(node.getValue().getClass().getSimpleName())
+ .append('(').append(node.getValue().getId()).append(')');
+ node.getAllLinks(true).forEach(child -> printNode(stringBuilder, child, indentation + " "));
+ }
+
+}
diff --git a/src/main/java/org/traccar/session/cache/CacheKey.java b/src/main/java/org/traccar/session/cache/CacheKey.java
index 23145e34b..f27d5fbf5 100644
--- a/src/main/java/org/traccar/session/cache/CacheKey.java
+++ b/src/main/java/org/traccar/session/cache/CacheKey.java
@@ -33,6 +33,10 @@ class CacheKey {
this.id = id;
}
+ public Class<? extends BaseModel> getClazz() {
+ return clazz;
+ }
+
public boolean classIs(Class<? extends BaseModel> clazz) {
return clazz.equals(this.clazz);
}
diff --git a/src/main/java/org/traccar/session/cache/CacheManager.java b/src/main/java/org/traccar/session/cache/CacheManager.java
index 9d2350012..064e5672f 100644
--- a/src/main/java/org/traccar/session/cache/CacheManager.java
+++ b/src/main/java/org/traccar/session/cache/CacheManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * 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.
@@ -15,8 +15,8 @@
*/
package org.traccar.session.cache;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import org.traccar.broadcast.BroadcastInterface;
import org.traccar.broadcast.BroadcastService;
import org.traccar.config.Config;
@@ -30,8 +30,10 @@ import org.traccar.model.Group;
import org.traccar.model.GroupedModel;
import org.traccar.model.Maintenance;
import org.traccar.model.Notification;
+import org.traccar.model.ObjectOperation;
+import org.traccar.model.Permission;
import org.traccar.model.Position;
-import org.traccar.model.ScheduledModel;
+import org.traccar.model.Schedulable;
import org.traccar.model.Server;
import org.traccar.model.User;
import org.traccar.storage.Storage;
@@ -40,19 +42,10 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
@@ -60,10 +53,8 @@ import java.util.stream.Collectors;
@Singleton
public class CacheManager implements BroadcastInterface {
- private static final Logger LOGGER = LoggerFactory.getLogger(CacheManager.class);
- private static final int GROUP_DEPTH_LIMIT = 3;
- private static final Collection<Class<? extends BaseModel>> CLASSES = Arrays.asList(
- Attribute.class, Driver.class, Geofence.class, Maintenance.class, Notification.class);
+ private static final Set<Class<? extends BaseModel>> GROUPED_CLASSES =
+ Set.of(Attribute.class, Driver.class, Geofence.class, Maintenance.class, Notification.class);
private final Config config;
private final Storage storage;
@@ -71,24 +62,26 @@ public class CacheManager implements BroadcastInterface {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
- private final Map<CacheKey, CacheValue> deviceCache = new HashMap<>();
- private final Map<Long, Integer> deviceReferences = new HashMap<>();
- private final Map<Long, Map<Class<? extends BaseModel>, Set<Long>>> deviceLinks = new HashMap<>();
- private final Map<Long, Position> devicePositions = new HashMap<>();
+ private final CacheGraph graph = new CacheGraph();
private Server server;
- private final Map<Long, List<User>> notificationUsers = new HashMap<>();
+ private final Map<Long, Position> devicePositions = new HashMap<>();
+ private final Map<Long, AtomicInteger> deviceReferences = new HashMap<>();
@Inject
public CacheManager(Config config, Storage storage, BroadcastService broadcastService) throws StorageException {
this.config = config;
this.storage = storage;
this.broadcastService = broadcastService;
- invalidateServer();
- invalidateUsers();
+ server = storage.getObject(Server.class, new Request(new Columns.All()));
broadcastService.registerListener(this);
}
+ @Override
+ public String toString() {
+ return graph.toString();
+ }
+
public Config getConfig() {
return config;
}
@@ -96,29 +89,17 @@ public class CacheManager implements BroadcastInterface {
public <T extends BaseModel> T getObject(Class<T> clazz, long id) {
try {
lock.readLock().lock();
- var cacheValue = deviceCache.get(new CacheKey(clazz, id));
- return cacheValue != null ? cacheValue.getValue() : null;
+ return graph.getObject(clazz, id);
} finally {
lock.readLock().unlock();
}
}
- public <T extends BaseModel> List<T> getDeviceObjects(long deviceId, Class<T> clazz) {
+ public <T extends BaseModel> Set<T> getDeviceObjects(long deviceId, Class<T> clazz) {
try {
lock.readLock().lock();
- var links = deviceLinks.get(deviceId);
- if (links != null) {
- return links.getOrDefault(clazz, new LinkedHashSet<>()).stream()
- .map(id -> {
- var cacheValue = deviceCache.get(new CacheKey(clazz, id));
- return cacheValue != null ? cacheValue.<T>getValue() : null;
- })
- .filter(Objects::nonNull)
- .collect(Collectors.toList());
- } else {
- LOGGER.warn("Device {} cache missing", deviceId);
- return Collections.emptyList();
- }
+ return graph.getObjects(Device.class, deviceId, clazz, Set.of(Group.class), true)
+ .collect(Collectors.toUnmodifiableSet());
} finally {
lock.readLock().unlock();
}
@@ -142,37 +123,45 @@ public class CacheManager implements BroadcastInterface {
}
}
- public List<User> getNotificationUsers(long notificationId, long deviceId) {
+ public Set<User> getNotificationUsers(long notificationId, long deviceId) {
try {
lock.readLock().lock();
- var users = deviceLinks.get(deviceId).get(User.class).stream()
+ Set<User> deviceUsers = getDeviceObjects(deviceId, User.class);
+ return graph.getObjects(Notification.class, notificationId, User.class, Set.of(), false)
+ .filter(deviceUsers::contains)
.collect(Collectors.toUnmodifiableSet());
- return notificationUsers.getOrDefault(notificationId, new LinkedList<>()).stream()
- .filter(user -> users.contains(user.getId()))
- .collect(Collectors.toUnmodifiableList());
} finally {
lock.readLock().unlock();
}
}
- public Driver findDriverByUniqueId(long deviceId, String driverUniqueId) {
- return getDeviceObjects(deviceId, Driver.class).stream()
- .filter(driver -> driver.getUniqueId().equals(driverUniqueId))
- .findFirst()
- .orElse(null);
+ public Set<Notification> getDeviceNotifications(long deviceId) {
+ try {
+ lock.readLock().lock();
+ var direct = graph.getObjects(Device.class, deviceId, Notification.class, Set.of(Group.class), true)
+ .map(BaseModel::getId)
+ .collect(Collectors.toUnmodifiableSet());
+ return graph.getObjects(Device.class, deviceId, Notification.class, Set.of(Group.class, User.class), true)
+ .filter(notification -> notification.getAlways() || direct.contains(notification.getId()))
+ .collect(Collectors.toUnmodifiableSet());
+ } finally {
+ lock.readLock().unlock();
+ }
}
- public void addDevice(long deviceId) throws StorageException {
+ public void addDevice(long deviceId) throws Exception {
try {
lock.writeLock().lock();
- Integer references = deviceReferences.get(deviceId);
- if (references != null) {
- references += 1;
- } else {
- unsafeAddDevice(deviceId);
- references = 1;
+ if (deviceReferences.computeIfAbsent(deviceId, k -> new AtomicInteger()).getAndIncrement() <= 0) {
+ Device device = storage.getObject(Device.class, new Request(
+ new Columns.All(), new Condition.Equals("id", deviceId)));
+ graph.addObject(device);
+ initializeCache(device);
+ if (device.getPositionId() > 0) {
+ devicePositions.put(deviceId, storage.getObject(Position.class, new Request(
+ new Columns.All(), new Condition.Equals("id", device.getPositionId()))));
+ }
}
- deviceReferences.put(deviceId, references);
} finally {
lock.writeLock().unlock();
}
@@ -181,15 +170,10 @@ public class CacheManager implements BroadcastInterface {
public void removeDevice(long deviceId) {
try {
lock.writeLock().lock();
- Integer references = deviceReferences.get(deviceId);
- if (references != null) {
- references -= 1;
- if (references <= 0) {
- unsafeRemoveDevice(deviceId);
- deviceReferences.remove(deviceId);
- } else {
- deviceReferences.put(deviceId, references);
- }
+ if (deviceReferences.computeIfAbsent(deviceId, k -> new AtomicInteger()).incrementAndGet() <= 0) {
+ graph.removeObject(Device.class, deviceId);
+ devicePositions.remove(deviceId);
+ deviceReferences.remove(deviceId);
}
} finally {
lock.writeLock().unlock();
@@ -199,7 +183,7 @@ public class CacheManager implements BroadcastInterface {
public void updatePosition(Position position) {
try {
lock.writeLock().lock();
- if (deviceLinks.containsKey(position.getDeviceId())) {
+ if (deviceReferences.containsKey(position.getDeviceId())) {
devicePositions.put(position.getDeviceId(), position);
}
} finally {
@@ -208,218 +192,140 @@ public class CacheManager implements BroadcastInterface {
}
@Override
- public void invalidateObject(boolean local, Class<? extends BaseModel> clazz, long id) {
- try {
- var object = storage.getObject(clazz, new Request(
- new Columns.All(), new Condition.Equals("id", id)));
- if (object != null) {
- updateOrInvalidate(local, object);
- } else {
- invalidate(clazz, id);
- }
- } catch (StorageException e) {
- throw new RuntimeException(e);
- }
- }
-
- public <T extends BaseModel> void updateOrInvalidate(boolean local, T object) throws StorageException {
+ public <T extends BaseModel> void invalidateObject(
+ boolean local, Class<T> clazz, long id, ObjectOperation operation) throws Exception {
if (local) {
- broadcastService.invalidateObject(true, object.getClass(), object.getId());
+ broadcastService.invalidateObject(true, clazz, id, operation);
}
- if (object instanceof Server) {
- invalidateServer();
+ if (operation == ObjectOperation.DELETE) {
+ graph.removeObject(clazz, id);
+ }
+ if (operation != ObjectOperation.UPDATE) {
return;
}
- if (object instanceof User) {
- invalidateUsers();
+
+ if (clazz.equals(Server.class)) {
+ server = storage.getObject(Server.class, new Request(new Columns.All()));
return;
}
- boolean invalidate = false;
- var before = getObject(object.getClass(), object.getId());
+ var after = storage.getObject(clazz, new Request(new Columns.All(), new Condition.Equals("id", id)));
+ if (after == null) {
+ return;
+ }
+ var before = getObject(after.getClass(), after.getId());
if (before == null) {
return;
- } else if (object instanceof GroupedModel) {
- if (((GroupedModel) before).getGroupId() != ((GroupedModel) object).getGroupId()) {
- invalidate = true;
- }
- } else if (object instanceof ScheduledModel) {
- if (((ScheduledModel) before).getCalendarId() != ((ScheduledModel) object).getCalendarId()) {
- invalidate = true;
- }
}
- if (invalidate) {
- invalidate(object.getClass(), object.getId());
- } else {
- try {
- lock.writeLock().lock();
- deviceCache.get(new CacheKey(object.getClass(), object.getId())).setValue(object);
- } finally {
- lock.writeLock().unlock();
+
+ if (after instanceof GroupedModel) {
+ long beforeGroupId = ((GroupedModel) before).getGroupId();
+ long afterGroupId = ((GroupedModel) after).getGroupId();
+ if (beforeGroupId != afterGroupId) {
+ if (beforeGroupId > 0) {
+ invalidatePermission(clazz, id, Group.class, beforeGroupId, false);
+ }
+ if (afterGroupId > 0) {
+ invalidatePermission(clazz, id, Group.class, afterGroupId, true);
+ }
}
+ } else if (after instanceof Schedulable) {
+ long beforeCalendarId = ((Schedulable) before).getCalendarId();
+ long afterCalendarId = ((Schedulable) after).getCalendarId();
+ if (beforeCalendarId != afterCalendarId) {
+ if (beforeCalendarId > 0) {
+ invalidatePermission(clazz, id, Calendar.class, beforeCalendarId, false);
+ }
+ if (afterCalendarId > 0) {
+ invalidatePermission(clazz, id, Calendar.class, afterCalendarId, true);
+ }
+ }
+ // TODO handle notification always change
}
- }
- public <T extends BaseModel> void invalidate(Class<T> clazz, long id) throws StorageException {
- invalidate(new CacheKey(clazz, id));
+ graph.updateObject(after);
}
@Override
- public void invalidatePermission(
- boolean local,
- Class<? extends BaseModel> clazz1, long id1,
- Class<? extends BaseModel> clazz2, long id2) {
+ public <T1 extends BaseModel, T2 extends BaseModel> void invalidatePermission(
+ boolean local, Class<T1> clazz1, long id1, Class<T2> clazz2, long id2, boolean link) throws Exception {
if (local) {
- broadcastService.invalidatePermission(true, clazz1, id1, clazz2, id2);
+ broadcastService.invalidatePermission(true, clazz1, id1, clazz2, id2, link);
}
- try {
- invalidate(new CacheKey(clazz1, id1), new CacheKey(clazz2, id2));
- } catch (StorageException e) {
- throw new RuntimeException(e);
+ if (clazz1.equals(User.class) && GroupedModel.class.isAssignableFrom(clazz2)) {
+ invalidatePermission(clazz2, id2, clazz1, id1, link);
+ } else {
+ invalidatePermission(clazz1, id1, clazz2, id2, link);
}
}
- private void invalidateServer() throws StorageException {
- server = storage.getObject(Server.class, new Request(new Columns.All()));
- }
+ private <T1 extends BaseModel, T2 extends BaseModel> void invalidatePermission(
+ Class<T1> fromClass, long fromId, Class<T2> toClass, long toId, boolean link) throws Exception {
- private void invalidateUsers() throws StorageException {
- notificationUsers.clear();
- Map<Long, User> users = new HashMap<>();
- storage.getObjects(User.class, new Request(new Columns.All()))
- .forEach(user -> users.put(user.getId(), user));
- storage.getPermissions(User.class, Notification.class).forEach(permission -> {
- long notificationId = permission.getPropertyId();
- var user = users.get(permission.getOwnerId());
- notificationUsers.computeIfAbsent(notificationId, k -> new LinkedList<>()).add(user);
- });
- }
+ boolean groupLink = GroupedModel.class.isAssignableFrom(fromClass) && toClass.equals(Group.class);
+ boolean calendarLink = Schedulable.class.isAssignableFrom(fromClass) && toClass.equals(Calendar.class);
+ boolean userLink = fromClass.equals(User.class) && toClass.equals(Notification.class);
- private void addObject(long deviceId, BaseModel object) {
- deviceCache.computeIfAbsent(new CacheKey(object), k -> new CacheValue(object)).retain(deviceId);
- }
+ boolean groupedLinks = GroupedModel.class.isAssignableFrom(fromClass)
+ && (GROUPED_CLASSES.contains(toClass) || toClass.equals(User.class));
+
+ if (!groupLink && !calendarLink && !userLink && !groupedLinks) {
+ return;
+ }
- private void unsafeAddDevice(long deviceId) throws StorageException {
- Map<Class<? extends BaseModel>, Set<Long>> links = new HashMap<>();
-
- Device device = storage.getObject(Device.class, new Request(
- new Columns.All(), new Condition.Equals("id", deviceId)));
- if (device != null) {
- addObject(deviceId, device);
-
- int groupDepth = 0;
- long groupId = device.getGroupId();
- while (groupDepth < GROUP_DEPTH_LIMIT && groupId > 0) {
- Group group = storage.getObject(Group.class, new Request(
- new Columns.All(), new Condition.Equals("id", groupId)));
- links.computeIfAbsent(Group.class, k -> new LinkedHashSet<>()).add(group.getId());
- addObject(deviceId, group);
- groupId = group.getGroupId();
- groupDepth += 1;
+ if (link) {
+ BaseModel object = storage.getObject(toClass, new Request(
+ new Columns.All(), new Condition.Equals("id", toId)));
+ if (!graph.addLink(fromClass, fromId, object)) {
+ initializeCache(object);
}
+ } else {
+ graph.removeLink(fromClass, fromId, toClass, toId);
+ }
+ }
- for (Class<? extends BaseModel> clazz : CLASSES) {
- var objects = storage.getObjects(clazz, new Request(
- new Columns.All(), new Condition.Permission(Device.class, deviceId, clazz)));
- links.put(clazz, objects.stream().map(BaseModel::getId).collect(Collectors.toSet()));
- for (var object : objects) {
- addObject(deviceId, object);
- if (object instanceof ScheduledModel) {
- var scheduled = (ScheduledModel) object;
- if (scheduled.getCalendarId() > 0) {
- var calendar = storage.getObject(Calendar.class, new Request(
- new Columns.All(), new Condition.Equals("id", scheduled.getCalendarId())));
- links.computeIfAbsent(Notification.class, k -> new LinkedHashSet<>())
- .add(calendar.getId());
- addObject(deviceId, calendar);
- }
- }
+ private void initializeCache(BaseModel object) throws Exception {
+ if (object instanceof User) {
+ for (Permission permission : storage.getPermissions(User.class, Notification.class)) {
+ if (permission.getOwnerId() == object.getId()) {
+ invalidatePermission(
+ permission.getOwnerClass(), permission.getOwnerId(),
+ permission.getPropertyClass(), permission.getPropertyId(), true);
}
}
+ } else {
+ if (object instanceof GroupedModel) {
+ long groupId = ((GroupedModel) object).getGroupId();
+ if (groupId > 0) {
+ invalidatePermission(object.getClass(), object.getId(), Group.class, groupId, true);
+ }
- var users = storage.getObjects(User.class, new Request(
- new Columns.All(), new Condition.Permission(User.class, Device.class, deviceId)));
- links.put(User.class, users.stream().map(BaseModel::getId).collect(Collectors.toSet()));
- for (var user : users) {
- addObject(deviceId, user);
- var notifications = storage.getObjects(Notification.class, new Request(
- new Columns.All(),
- new Condition.Permission(User.class, user.getId(), Notification.class))).stream()
- .filter(Notification::getAlways)
- .collect(Collectors.toList());
- for (var notification : notifications) {
- links.computeIfAbsent(Notification.class, k -> new LinkedHashSet<>())
- .add(notification.getId());
- addObject(deviceId, notification);
- if (notification.getCalendarId() > 0) {
- var calendar = storage.getObject(Calendar.class, new Request(
- new Columns.All(), new Condition.Equals("id", notification.getCalendarId())));
- links.computeIfAbsent(Notification.class, k -> new LinkedHashSet<>())
- .add(calendar.getId());
- addObject(deviceId, calendar);
+ for (Permission permission : storage.getPermissions(User.class, object.getClass())) {
+ if (permission.getPropertyId() == object.getId()) {
+ invalidatePermission(
+ object.getClass(), object.getId(), User.class, permission.getOwnerId(), true);
}
}
- }
- deviceLinks.put(deviceId, links);
-
- if (device.getPositionId() > 0) {
- devicePositions.put(deviceId, storage.getObject(Position.class, new Request(
- new Columns.All(), new Condition.Equals("id", device.getPositionId()))));
+ for (Class<? extends BaseModel> clazz : GROUPED_CLASSES) {
+ for (Permission permission : storage.getPermissions(object.getClass(), clazz)) {
+ if (permission.getOwnerId() == object.getId()) {
+ invalidatePermission(
+ object.getClass(), object.getId(), clazz, permission.getPropertyId(), true);
+ }
+ }
+ }
}
- }
- }
-
- private void unsafeRemoveDevice(long deviceId) {
- deviceCache.remove(new CacheKey(Device.class, deviceId));
- deviceLinks.remove(deviceId).forEach((clazz, ids) -> ids.forEach(id -> {
- var key = new CacheKey(clazz, id);
- deviceCache.computeIfPresent(key, (k, value) -> {
- value.release(deviceId);
- return value.getReferences().size() > 0 ? value : null;
- });
- }));
- devicePositions.remove(deviceId);
- }
-
- private void invalidate(CacheKey... keys) throws StorageException {
- try {
- lock.writeLock().lock();
- unsafeInvalidate(keys);
- } finally {
- lock.writeLock().unlock();
- }
- }
- private void unsafeInvalidate(CacheKey[] keys) throws StorageException {
- boolean invalidateServer = false;
- boolean invalidateUsers = false;
- Set<Long> linkedDevices = new HashSet<>();
- for (var key : keys) {
- if (key.classIs(Server.class)) {
- invalidateServer = true;
- } else {
- if (key.classIs(User.class) || key.classIs(Notification.class)) {
- invalidateUsers = true;
+ if (object instanceof Schedulable) {
+ long calendarId = ((Schedulable) object).getCalendarId();
+ if (calendarId > 0) {
+ invalidatePermission(object.getClass(), object.getId(), Calendar.class, calendarId, true);
}
- deviceCache.computeIfPresent(key, (k, value) -> {
- linkedDevices.addAll(value.getReferences());
- return value;
- });
}
}
- for (long deviceId : linkedDevices) {
- unsafeRemoveDevice(deviceId);
- unsafeAddDevice(deviceId);
- }
- if (invalidateServer) {
- invalidateServer();
- }
- if (invalidateUsers) {
- invalidateUsers();
- }
}
}
diff --git a/src/main/java/org/traccar/session/cache/CacheNode.java b/src/main/java/org/traccar/session/cache/CacheNode.java
new file mode 100644
index 000000000..7b584f81a
--- /dev/null
+++ b/src/main/java/org/traccar/session/cache/CacheNode.java
@@ -0,0 +1,40 @@
+package org.traccar.session.cache;
+
+import org.traccar.model.BaseModel;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+public class CacheNode {
+
+ private BaseModel value;
+
+ private final Map<Class<? extends BaseModel>, Set<CacheNode>> links = new HashMap<>();
+ private final Map<Class<? extends BaseModel>, Set<CacheNode>> backlinks = new HashMap<>();
+
+ public CacheNode(BaseModel value) {
+ this.value = value;
+ }
+
+ public BaseModel getValue() {
+ return value;
+ }
+
+ public void setValue(BaseModel value) {
+ this.value = value;
+ }
+
+ public Set<CacheNode> getLinks(Class<? extends BaseModel> clazz, boolean forward) {
+ var map = forward ? links : backlinks;
+ return map.computeIfAbsent(clazz, k -> new HashSet<>());
+ }
+
+ public Stream<CacheNode> getAllLinks(boolean forward) {
+ var map = forward ? links : backlinks;
+ return map.values().stream().flatMap(Set::stream);
+ }
+
+}
diff --git a/src/main/java/org/traccar/session/cache/CacheValue.java b/src/main/java/org/traccar/session/cache/CacheValue.java
deleted file mode 100644
index 1f0383ce5..000000000
--- a/src/main/java/org/traccar/session/cache/CacheValue.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.session.cache;
-
-import org.traccar.model.BaseModel;
-
-import java.util.HashSet;
-import java.util.Set;
-
-class CacheValue {
-
- private BaseModel value;
- private final Set<Long> references = new HashSet<>();
-
- CacheValue(BaseModel value) {
- this.value = value;
- }
-
- public void retain(long deviceId) {
- references.add(deviceId);
- }
-
- public void release(long deviceId) {
- references.remove(deviceId);
- }
-
- @SuppressWarnings("unchecked")
- public <T extends BaseModel> T getValue() {
- return (T) value;
- }
-
- public void setValue(BaseModel value) {
- this.value = value;
- }
-
- public Set<Long> getReferences() {
- return references;
- }
-
-}
diff --git a/src/main/java/org/traccar/session/cache/WeakValueMap.java b/src/main/java/org/traccar/session/cache/WeakValueMap.java
new file mode 100644
index 000000000..8323e2c30
--- /dev/null
+++ b/src/main/java/org/traccar/session/cache/WeakValueMap.java
@@ -0,0 +1,44 @@
+/*
+ * 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.session.cache;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Map;
+
+public class WeakValueMap<K, V> {
+
+ private final Map<K, WeakReference<V>> map = new HashMap<>();
+
+ public void put(K key, V value) {
+ map.put(key, new WeakReference<>(value));
+ }
+
+ public V get(K key) {
+ WeakReference<V> weakReference = map.get(key);
+ return (weakReference != null) ? weakReference.get() : null;
+ }
+
+ public V remove(K key) {
+ WeakReference<V> weakReference = map.remove(key);
+ return (weakReference != null) ? weakReference.get() : null;
+ }
+
+ private void clean() {
+ map.entrySet().removeIf(entry -> entry.getValue().get() == null);
+ }
+
+}
diff --git a/src/main/java/org/traccar/session/state/OverspeedProcessor.java b/src/main/java/org/traccar/session/state/OverspeedProcessor.java
index 62f6a3de2..221b51ff5 100644
--- a/src/main/java/org/traccar/session/state/OverspeedProcessor.java
+++ b/src/main/java/org/traccar/session/state/OverspeedProcessor.java
@@ -26,40 +26,46 @@ public final class OverspeedProcessor {
}
public static void updateState(
- OverspeedState state, Position position, double speedLimit, long minimalDuration, long geofenceId) {
+ OverspeedState state, Position position,
+ double speedLimit, double multiplier, long minimalDuration, long geofenceId) {
state.setEvent(null);
boolean oldState = state.getOverspeedState();
if (oldState) {
- boolean newState = position.getSpeed() > speedLimit;
+ boolean newState = position.getSpeed() > speedLimit * multiplier;
if (newState) {
- if (state.getOverspeedTime() != null) {
- long oldTime = state.getOverspeedTime().getTime();
- long newTime = position.getFixTime().getTime();
- if (newTime - oldTime > minimalDuration) {
-
- Event event = new Event(Event.TYPE_DEVICE_OVERSPEED, position);
- event.set(ATTRIBUTE_SPEED, position.getSpeed());
- event.set(Position.KEY_SPEED_LIMIT, speedLimit);
- event.setGeofenceId(state.getOverspeedGeofenceId());
-
- state.setOverspeedTime(null);
- state.setOverspeedGeofenceId(0);
- state.setEvent(event);
-
- }
- }
+ checkEvent(state, position, speedLimit, minimalDuration);
} else {
state.setOverspeedState(false);
state.setOverspeedTime(null);
state.setOverspeedGeofenceId(0);
}
- } else if (position != null && position.getSpeed() > speedLimit) {
+ } else if (position != null && position.getSpeed() > speedLimit * multiplier) {
state.setOverspeedState(true);
state.setOverspeedTime(position.getFixTime());
state.setOverspeedGeofenceId(geofenceId);
+
+ checkEvent(state, position, speedLimit, minimalDuration);
}
}
+ private static void checkEvent(OverspeedState state, Position position, double speedLimit, long minimalDuration) {
+ if (state.getOverspeedTime() != null) {
+ long oldTime = state.getOverspeedTime().getTime();
+ long newTime = position.getFixTime().getTime();
+ if (newTime - oldTime >= minimalDuration) {
+
+ Event event = new Event(Event.TYPE_DEVICE_OVERSPEED, position);
+ event.set(ATTRIBUTE_SPEED, position.getSpeed());
+ event.set(Position.KEY_SPEED_LIMIT, speedLimit);
+ event.setGeofenceId(state.getOverspeedGeofenceId());
+
+ state.setOverspeedTime(null);
+ state.setOverspeedGeofenceId(0);
+ state.setEvent(event);
+
+ }
+ }
+ }
}