diff options
Diffstat (limited to 'src/main/java/org/traccar/database')
26 files changed, 575 insertions, 2906 deletions
diff --git a/src/main/java/org/traccar/database/ActiveDevice.java b/src/main/java/org/traccar/database/ActiveDevice.java deleted file mode 100644 index c05d56ad2..000000000 --- a/src/main/java/org/traccar/database/ActiveDevice.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2015 - 2020 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.database; - -import io.netty.channel.Channel; -import io.netty.handler.codec.http.HttpRequestDecoder; -import org.traccar.BasePipelineFactory; -import org.traccar.Protocol; -import org.traccar.model.Command; - -import java.net.SocketAddress; - -public class ActiveDevice { - - private final long deviceId; - private final Protocol protocol; - private final Channel channel; - private final SocketAddress remoteAddress; - private final boolean supportsLiveCommands; - - public ActiveDevice(long deviceId, Protocol protocol, Channel channel, SocketAddress remoteAddress) { - this.deviceId = deviceId; - this.protocol = protocol; - this.channel = channel; - this.remoteAddress = remoteAddress; - supportsLiveCommands = BasePipelineFactory.getHandler(channel.pipeline(), HttpRequestDecoder.class) == null; - } - - public Channel getChannel() { - return channel; - } - - public long getDeviceId() { - return deviceId; - } - - public boolean supportsLiveCommands() { - return supportsLiveCommands; - } - - public void sendCommand(Command command) { - protocol.sendDataCommand(channel, remoteAddress, command); - } - -} diff --git a/src/main/java/org/traccar/database/AttributesManager.java b/src/main/java/org/traccar/database/AttributesManager.java deleted file mode 100644 index 28816645a..000000000 --- a/src/main/java/org/traccar/database/AttributesManager.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2017 Anton Tananaev (anton@traccar.org) - * Copyright 2017 Andrey Kunitsyn (andrey@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.database; - -import org.traccar.model.Attribute; - -public class AttributesManager extends ExtendedObjectManager<Attribute> { - - public AttributesManager(DataManager dataManager) { - super(dataManager, Attribute.class); - } - - @Override - public void updateCachedItem(Attribute attribute) { - Attribute cachedAttribute = getById(attribute.getId()); - cachedAttribute.setDescription(attribute.getDescription()); - cachedAttribute.setAttribute(attribute.getAttribute()); - cachedAttribute.setExpression(attribute.getExpression()); - cachedAttribute.setType(attribute.getType()); - } - -} diff --git a/src/main/java/org/traccar/database/BaseObjectManager.java b/src/main/java/org/traccar/database/BaseObjectManager.java deleted file mode 100644 index dd8b3bae4..000000000 --- a/src/main/java/org/traccar/database/BaseObjectManager.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) - * Copyright 2017 Andrey Kunitsyn (andrey@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.database; - -import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.model.BaseModel; -import org.traccar.storage.StorageException; - -public class BaseObjectManager<T extends BaseModel> { - - private static final Logger LOGGER = LoggerFactory.getLogger(BaseObjectManager.class); - - private final ReadWriteLock lock = new ReentrantReadWriteLock(); - - private final DataManager dataManager; - - private final Class<T> baseClass; - private Map<Long, T> items; - - protected BaseObjectManager(DataManager dataManager, Class<T> baseClass) { - this.dataManager = dataManager; - this.baseClass = baseClass; - refreshItems(); - } - - protected final void readLock() { - lock.readLock().lock(); - } - - protected final void readUnlock() { - lock.readLock().unlock(); - } - - protected final void writeLock() { - lock.writeLock().lock(); - } - - protected final void writeUnlock() { - lock.writeLock().unlock(); - } - - protected final DataManager getDataManager() { - return dataManager; - } - - protected final Class<T> getBaseClass() { - return baseClass; - } - - public T getById(long itemId) { - try { - readLock(); - return items.get(itemId); - } finally { - readUnlock(); - } - } - - public void refreshItems() { - if (dataManager != null) { - try { - writeLock(); - Collection<T> databaseItems = dataManager.getObjects(baseClass); - if (items == null) { - items = new ConcurrentHashMap<>(databaseItems.size()); - } - Set<Long> databaseItemIds = new HashSet<>(); - for (T item : databaseItems) { - databaseItemIds.add(item.getId()); - if (items.containsKey(item.getId())) { - updateCachedItem(item); - } else { - addNewItem(item); - } - } - for (Long cachedItemId : items.keySet()) { - if (!databaseItemIds.contains(cachedItemId)) { - removeCachedItem(cachedItemId); - } - } - } catch (StorageException error) { - LOGGER.warn("Error refreshing items", error); - } finally { - writeUnlock(); - } - } - } - - protected void addNewItem(T item) { - try { - writeLock(); - items.put(item.getId(), item); - } finally { - writeUnlock(); - } - } - - public void addItem(T item) throws StorageException { - dataManager.addObject(item); - addNewItem(item); - } - - protected void updateCachedItem(T item) { - try { - writeLock(); - items.put(item.getId(), item); - } finally { - writeUnlock(); - } - } - - public void updateItem(T item) throws StorageException { - dataManager.updateObject(item); - updateCachedItem(item); - } - - protected void removeCachedItem(long itemId) { - try { - writeLock(); - items.remove(itemId); - } finally { - writeUnlock(); - } - } - - public void removeItem(long itemId) throws StorageException { - BaseModel item = getById(itemId); - if (item != null) { - dataManager.removeObject(baseClass, itemId); - removeCachedItem(itemId); - } - } - - public final Collection<T> getItems(Set<Long> itemIds) { - Collection<T> result = new LinkedList<>(); - for (long itemId : itemIds) { - T item = getById(itemId); - if (item != null) { - result.add(item); - } - } - return result; - } - - public Set<Long> getAllItems() { - try { - readLock(); - return items.keySet(); - } finally { - readUnlock(); - } - } - -} diff --git a/src/main/java/org/traccar/database/CalendarManager.java b/src/main/java/org/traccar/database/CalendarManager.java deleted file mode 100644 index 44ced1082..000000000 --- a/src/main/java/org/traccar/database/CalendarManager.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org) - * Copyright 2016 - 2017 Andrey Kunitsyn (andrey@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.database; - -import org.traccar.model.Calendar; - -public class CalendarManager extends SimpleObjectManager<Calendar> { - - public CalendarManager(DataManager dataManager) { - super(dataManager, Calendar.class); - } - -} diff --git a/src/main/java/org/traccar/database/CommandsManager.java b/src/main/java/org/traccar/database/CommandsManager.java index 843c89e82..fb8f2f9d6 100644 --- a/src/main/java/org/traccar/database/CommandsManager.java +++ b/src/main/java/org/traccar/database/CommandsManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,177 +16,112 @@ */ package org.traccar.database; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.traccar.BaseProtocol; -import org.traccar.Context; +import org.traccar.ServerManager; +import org.traccar.broadcast.BroadcastInterface; +import org.traccar.broadcast.BroadcastService; import org.traccar.model.Command; -import org.traccar.model.Typed; +import org.traccar.model.Device; import org.traccar.model.Position; - -public class CommandsManager extends ExtendedObjectManager<Command> { - - private static final Logger LOGGER = LoggerFactory.getLogger(CommandsManager.class); - - private final Map<Long, Queue<Command>> deviceQueues = new ConcurrentHashMap<>(); - - private final boolean queueing; - - public CommandsManager(DataManager dataManager, boolean queueing) { - super(dataManager, Command.class); - this.queueing = queueing; - } - - public boolean checkDeviceCommand(long deviceId, long commandId) { - return !getAllDeviceItems(deviceId).contains(commandId); +import org.traccar.model.QueuedCommand; +import org.traccar.session.ConnectionManager; +import org.traccar.session.DeviceSession; +import org.traccar.sms.SmsManager; +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 jakarta.annotation.Nullable; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import java.util.Collection; +import java.util.stream.Collectors; + +@Singleton +public class CommandsManager implements BroadcastInterface { + + private final Storage storage; + private final ServerManager serverManager; + private final SmsManager smsManager; + private final ConnectionManager connectionManager; + private final BroadcastService broadcastService; + + @Inject + public CommandsManager( + Storage storage, ServerManager serverManager, @Nullable SmsManager smsManager, + ConnectionManager connectionManager, BroadcastService broadcastService) { + this.storage = storage; + this.serverManager = serverManager; + this.smsManager = smsManager; + this.connectionManager = connectionManager; + this.broadcastService = broadcastService; + broadcastService.registerListener(this); } public boolean sendCommand(Command command) throws Exception { long deviceId = command.getDeviceId(); - if (command.getId() != 0) { - command = getById(command.getId()).clone(); - command.setDeviceId(deviceId); - } if (command.getTextChannel()) { - Position lastPosition = Context.getIdentityManager().getLastPosition(deviceId); - String phone = Context.getIdentityManager().getById(deviceId).getPhone(); - if (lastPosition != null) { - BaseProtocol protocol = Context.getServerManager().getProtocol(lastPosition.getProtocol()); - protocol.sendTextCommand(phone, command); + if (smsManager == null) { + throw new RuntimeException("SMS not configured"); + } + Device device = storage.getObject(Device.class, new Request( + new Columns.Include("positionId", "phone"), new Condition.Equals("id", deviceId))); + Position position = storage.getObject(Position.class, new Request( + new Columns.All(), new Condition.Equals("id", device.getPositionId()))); + if (position != null) { + BaseProtocol protocol = serverManager.getProtocol(position.getProtocol()); + protocol.sendTextCommand(device.getPhone(), command); } else if (command.getType().equals(Command.TYPE_CUSTOM)) { - if (Context.getSmsManager() != null) { - Context.getSmsManager().sendMessageSync(phone, command.getString(Command.KEY_DATA), true); - } else { - throw new RuntimeException("SMS is not enabled"); - } + smsManager.sendMessage(device.getPhone(), command.getString(Command.KEY_DATA), true); } else { throw new RuntimeException("Command " + command.getType() + " is not supported"); } } else { - ActiveDevice activeDevice = Context.getConnectionManager().getActiveDevice(deviceId); - if (activeDevice != null) { - if (activeDevice.supportsLiveCommands()) { - activeDevice.sendCommand(command); - } else { - getDeviceQueue(deviceId).add(command); - return false; - } - } else if (!queueing) { - throw new RuntimeException("Device is not online"); + DeviceSession deviceSession = connectionManager.getDeviceSession(deviceId); + if (deviceSession != null && deviceSession.supportsLiveCommands()) { + deviceSession.sendCommand(command); } else { - getDeviceQueue(deviceId).add(command); + storage.addObject(QueuedCommand.fromCommand(command), new Request(new Columns.Exclude("id"))); + broadcastService.updateCommand(true, deviceId); return false; } } return true; } - public Collection<Long> getSupportedCommands(long deviceId) { - List<Long> result = new ArrayList<>(); - Position lastPosition = Context.getIdentityManager().getLastPosition(deviceId); - for (long commandId : getAllDeviceItems(deviceId)) { - Command command = getById(commandId); - if (lastPosition != null) { - BaseProtocol protocol = Context.getServerManager().getProtocol(lastPosition.getProtocol()); - if (command.getTextChannel() && protocol.getSupportedTextCommands().contains(command.getType()) - || !command.getTextChannel() - && protocol.getSupportedDataCommands().contains(command.getType())) { - result.add(commandId); - } - } else if (command.getType().equals(Command.TYPE_CUSTOM)) { - result.add(commandId); - } - } - return result; - } - - public Collection<Typed> getCommandTypes(long deviceId, boolean textChannel) { - Position lastPosition = Context.getIdentityManager().getLastPosition(deviceId); - if (lastPosition != null) { - return getCommandTypes(lastPosition.getProtocol(), textChannel); - } else { - return Collections.singletonList(new Typed(Command.TYPE_CUSTOM)); - } - } - - public Collection<Typed> getCommandTypes(String protocolName, boolean textChannel) { - List<Typed> result = new ArrayList<>(); - BaseProtocol protocol = Context.getServerManager().getProtocol(protocolName); - Collection<String> commands; - commands = textChannel ? protocol.getSupportedTextCommands() : protocol.getSupportedDataCommands(); - for (String commandKey : commands) { - result.add(new Typed(commandKey)); - } - return result; - } - - public Collection<Typed> getAllCommandTypes() { - List<Typed> result = new ArrayList<>(); - Field[] fields = Command.class.getDeclaredFields(); - for (Field field : fields) { - if (Modifier.isStatic(field.getModifiers()) && field.getName().startsWith("TYPE_")) { - try { - result.add(new Typed(field.get(null).toString())); - } catch (IllegalArgumentException | IllegalAccessException error) { - LOGGER.warn("Get command types error", error); - } - } - } - return result; - } - - private Queue<Command> getDeviceQueue(long deviceId) { - Queue<Command> deviceQueue; - try { - readLock(); - deviceQueue = deviceQueues.get(deviceId); - } finally { - readUnlock(); - } - if (deviceQueue != null) { - return deviceQueue; - } else { - try { - writeLock(); - return deviceQueues.computeIfAbsent(deviceId, key -> new ConcurrentLinkedQueue<>()); - } finally { - writeUnlock(); - } - } - } - public Collection<Command> readQueuedCommands(long deviceId) { return readQueuedCommands(deviceId, Integer.MAX_VALUE); } public Collection<Command> readQueuedCommands(long deviceId, int count) { - Queue<Command> deviceQueue; try { - readLock(); - deviceQueue = deviceQueues.get(deviceId); - } finally { - readUnlock(); + var commands = storage.getObjects(QueuedCommand.class, new Request( + new Columns.All(), + new Condition.Equals("deviceId", deviceId), + new Order("id", false, count))); + for (var command : commands) { + storage.removeObject(QueuedCommand.class, new Request( + new Condition.Equals("id", command.getId()))); + } + return commands.stream().map(QueuedCommand::toCommand).collect(Collectors.toList()); + } catch (StorageException e) { + throw new RuntimeException(e); } - Collection<Command> result = new ArrayList<>(); - if (deviceQueue != null) { - Command command = deviceQueue.poll(); - while (command != null && result.size() < count) { - result.add(command); - command = deviceQueue.poll(); + } + + @Override + public void updateCommand(boolean local, long deviceId) { + if (!local) { + DeviceSession deviceSession = connectionManager.getDeviceSession(deviceId); + if (deviceSession != null && deviceSession.supportsLiveCommands()) { + for (Command command : readQueuedCommands(deviceId)) { + deviceSession.sendCommand(command); + } } } - return result; } } diff --git a/src/main/java/org/traccar/database/ConnectionManager.java b/src/main/java/org/traccar/database/ConnectionManager.java deleted file mode 100644 index 359061f00..000000000 --- a/src/main/java/org/traccar/database/ConnectionManager.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright 2015 - 2020 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.database; - -import io.netty.channel.Channel; -import io.netty.util.Timeout; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.Context; -import org.traccar.GlobalTimer; -import org.traccar.Main; -import org.traccar.Protocol; -import org.traccar.config.Keys; -import org.traccar.handler.events.MotionEventHandler; -import org.traccar.handler.events.OverspeedEventHandler; -import org.traccar.model.Device; -import org.traccar.model.DeviceState; -import org.traccar.model.Event; -import org.traccar.model.Position; -import org.traccar.storage.StorageException; - -import java.net.SocketAddress; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; - -public class ConnectionManager { - - private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionManager.class); - - private final long deviceTimeout; - private final boolean updateDeviceState; - - private final Map<Long, ActiveDevice> activeDevices = new ConcurrentHashMap<>(); - private final Map<Long, Set<UpdateListener>> listeners = new ConcurrentHashMap<>(); - private final Map<Long, Timeout> timeouts = new ConcurrentHashMap<>(); - - public ConnectionManager() { - deviceTimeout = Context.getConfig().getLong(Keys.STATUS_TIMEOUT) * 1000; - updateDeviceState = Context.getConfig().getBoolean(Keys.STATUS_UPDATE_DEVICE_STATE); - } - - public void addActiveDevice(long deviceId, Protocol protocol, Channel channel, SocketAddress remoteAddress) { - activeDevices.put(deviceId, new ActiveDevice(deviceId, protocol, channel, remoteAddress)); - } - - public void removeActiveDevice(Channel channel) { - for (ActiveDevice activeDevice : activeDevices.values()) { - if (activeDevice.getChannel() == channel) { - updateDevice(activeDevice.getDeviceId(), Device.STATUS_OFFLINE, null); - activeDevices.remove(activeDevice.getDeviceId()); - break; - } - } - } - - public ActiveDevice getActiveDevice(long deviceId) { - return activeDevices.get(deviceId); - } - - public void updateDevice(final long deviceId, String status, Date time) { - Device device = Context.getIdentityManager().getById(deviceId); - if (device == null) { - return; - } - - String oldStatus = device.getStatus(); - device.setStatus(status); - - if (!status.equals(oldStatus)) { - String eventType; - Map<Event, Position> events = new HashMap<>(); - switch (status) { - case Device.STATUS_ONLINE: - eventType = Event.TYPE_DEVICE_ONLINE; - break; - case Device.STATUS_UNKNOWN: - eventType = Event.TYPE_DEVICE_UNKNOWN; - if (updateDeviceState) { - events.putAll(updateDeviceState(deviceId)); - } - break; - default: - eventType = Event.TYPE_DEVICE_OFFLINE; - if (updateDeviceState) { - events.putAll(updateDeviceState(deviceId)); - } - break; - } - events.put(new Event(eventType, deviceId), null); - Context.getNotificationManager().updateEvents(events); - } - - Timeout timeout = timeouts.remove(deviceId); - if (timeout != null) { - timeout.cancel(); - } - - if (time != null) { - device.setLastUpdate(time); - } - - if (status.equals(Device.STATUS_ONLINE)) { - timeouts.put(deviceId, GlobalTimer.getTimer().newTimeout(timeout1 -> { - if (!timeout1.isCancelled()) { - updateDevice(deviceId, Device.STATUS_UNKNOWN, null); - } - }, deviceTimeout, TimeUnit.MILLISECONDS)); - } - - try { - Context.getDeviceManager().updateDeviceStatus(device); - } catch (StorageException e) { - LOGGER.warn("Update device status error", e); - } - - updateDevice(device); - } - - public Map<Event, Position> updateDeviceState(long deviceId) { - DeviceState deviceState = Context.getDeviceManager().getDeviceState(deviceId); - Map<Event, Position> result = new HashMap<>(); - - Map<Event, Position> event = Main.getInjector() - .getInstance(MotionEventHandler.class).updateMotionState(deviceState); - if (event != null) { - result.putAll(event); - } - - event = Main.getInjector().getInstance(OverspeedEventHandler.class) - .updateOverspeedState(deviceState, Context.getDeviceManager(). - lookupAttributeDouble(deviceId, OverspeedEventHandler.ATTRIBUTE_SPEED_LIMIT, 0, true, false)); - if (event != null) { - result.putAll(event); - } - - return result; - } - - public synchronized void sendKeepalive() { - for (Set<UpdateListener> userListeners : listeners.values()) { - for (UpdateListener listener : userListeners) { - listener.onKeepalive(); - } - } - } - - public synchronized void updateDevice(Device device) { - for (long userId : Context.getPermissionsManager().getDeviceUsers(device.getId())) { - if (listeners.containsKey(userId)) { - for (UpdateListener listener : listeners.get(userId)) { - listener.onUpdateDevice(device); - } - } - } - } - - public synchronized void updatePosition(Position position) { - long deviceId = position.getDeviceId(); - - for (long userId : Context.getPermissionsManager().getDeviceUsers(deviceId)) { - if (listeners.containsKey(userId)) { - for (UpdateListener listener : listeners.get(userId)) { - listener.onUpdatePosition(position); - } - } - } - } - - public synchronized void updateEvent(long userId, Event event) { - if (listeners.containsKey(userId)) { - for (UpdateListener listener : listeners.get(userId)) { - listener.onUpdateEvent(event); - } - } - } - - public interface UpdateListener { - void onKeepalive(); - void onUpdateDevice(Device device); - void onUpdatePosition(Position position); - void onUpdateEvent(Event event); - } - - public synchronized void addListener(long userId, UpdateListener listener) { - if (!listeners.containsKey(userId)) { - listeners.put(userId, new HashSet<>()); - } - listeners.get(userId).add(listener); - } - - public synchronized void removeListener(long userId, UpdateListener listener) { - if (!listeners.containsKey(userId)) { - listeners.put(userId, new HashSet<>()); - } - listeners.get(userId).remove(listener); - } - -} diff --git a/src/main/java/org/traccar/database/DataManager.java b/src/main/java/org/traccar/database/DataManager.java deleted file mode 100644 index 9ac808a69..000000000 --- a/src/main/java/org/traccar/database/DataManager.java +++ /dev/null @@ -1,276 +0,0 @@ -/* - * Copyright 2012 - 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.database; - -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; -import liquibase.Contexts; -import liquibase.Liquibase; -import liquibase.database.Database; -import liquibase.database.DatabaseFactory; -import liquibase.exception.LiquibaseException; -import liquibase.resource.FileSystemResourceAccessor; -import liquibase.resource.ResourceAccessor; -import org.traccar.Context; -import org.traccar.config.Config; -import org.traccar.config.Keys; -import org.traccar.model.BaseModel; -import org.traccar.model.Device; -import org.traccar.model.Event; -import org.traccar.model.Permission; -import org.traccar.model.Position; -import org.traccar.model.Server; -import org.traccar.model.Statistics; -import org.traccar.model.User; -import org.traccar.storage.DatabaseStorage; -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.sql.DataSource; -import java.io.File; -import java.lang.reflect.Method; -import java.net.URL; -import java.util.Collection; -import java.util.Date; -import java.util.LinkedList; -import java.util.List; - -public class DataManager { - - private final Config config; - - private DataSource dataSource; - - public DataSource getDataSource() { - return dataSource; - } - - private final Storage storage; - - public Storage getStorage() { - return storage; - } - - private final boolean forceLdap; - - public DataManager(Config config) throws Exception { - this.config = config; - - forceLdap = config.getBoolean(Keys.LDAP_FORCE); - - initDatabase(); - initDatabaseSchema(); - - storage = new DatabaseStorage(dataSource); - } - - private void initDatabase() throws Exception { - - String driverFile = config.getString(Keys.DATABASE_DRIVER_FILE); - if (driverFile != null) { - ClassLoader classLoader = ClassLoader.getSystemClassLoader(); - try { - Method method = classLoader.getClass().getDeclaredMethod("addURL", URL.class); - method.setAccessible(true); - method.invoke(classLoader, new File(driverFile).toURI().toURL()); - } catch (NoSuchMethodException e) { - Method method = classLoader.getClass() - .getDeclaredMethod("appendToClassPathForInstrumentation", String.class); - method.setAccessible(true); - method.invoke(classLoader, driverFile); - } - } - - String driver = config.getString(Keys.DATABASE_DRIVER); - if (driver != null) { - Class.forName(driver); - } - - HikariConfig hikariConfig = new HikariConfig(); - hikariConfig.setDriverClassName(driver); - hikariConfig.setJdbcUrl(config.getString(Keys.DATABASE_URL)); - hikariConfig.setUsername(config.getString(Keys.DATABASE_USER)); - hikariConfig.setPassword(config.getString(Keys.DATABASE_PASSWORD)); - hikariConfig.setConnectionInitSql(config.getString(Keys.DATABASE_CHECK_CONNECTION)); - hikariConfig.setIdleTimeout(600000); - - int maxPoolSize = config.getInteger(Keys.DATABASE_MAX_POOL_SIZE); - if (maxPoolSize != 0) { - hikariConfig.setMaximumPoolSize(maxPoolSize); - } - - dataSource = new HikariDataSource(hikariConfig); - } - - private void initDatabaseSchema() throws LiquibaseException { - - if (config.hasKey(Keys.DATABASE_CHANGELOG)) { - - ResourceAccessor resourceAccessor = new FileSystemResourceAccessor(new File(".")); - - Database database = DatabaseFactory.getInstance().openDatabase( - config.getString(Keys.DATABASE_URL), - config.getString(Keys.DATABASE_USER), - config.getString(Keys.DATABASE_PASSWORD), - config.getString(Keys.DATABASE_DRIVER), - null, null, null, resourceAccessor); - - Liquibase liquibase = new Liquibase( - config.getString(Keys.DATABASE_CHANGELOG), resourceAccessor, database); - - liquibase.clearCheckSums(); - - liquibase.update(new Contexts()); - } - } - - public User login(String email, String password) throws StorageException { - User user = storage.getObject(User.class, new Request( - new Columns.Include("id", "login", "hashedPassword", "salt"), - new Condition.Or( - new Condition.Equals("email", "email", email.trim()), - new Condition.Equals("login", "email")))); - LdapProvider ldapProvider = Context.getLdapProvider(); - if (user != null) { - if (ldapProvider != null && user.getLogin() != null && ldapProvider.login(user.getLogin(), password) - || !forceLdap && user.isPasswordValid(password)) { - return user; - } - } else { - if (ldapProvider != null && ldapProvider.login(email, password)) { - user = ldapProvider.getUser(email); - Context.getUsersManager().addItem(user); - return user; - } - } - return null; - } - - public void updateUserPassword(User user) throws StorageException { - storage.updateObject(user, new Request( - new Columns.Include("hashedPassword", "salt"), - new Condition.Equals("id", "id"))); - } - - public void updateDeviceStatus(Device device) throws StorageException { - storage.updateObject(device, new Request( - new Columns.Include("lastUpdate"), - new Condition.Equals("id", "id"))); - } - - public Collection<Position> getPositions(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", deviceId), - new Condition.Between("fixTime", "from", from, "to", to)), - new Order("fixTime"))); - } - - public 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))); - } - - public void updateLatestPosition(Position position) throws StorageException { - Device device = new Device(); - device.setId(position.getDeviceId()); - device.setPositionId(position.getId()); - storage.updateObject(device, new Request( - new Columns.Include("positionId"), - new Condition.Equals("id", "id"))); - } - - public Collection<Position> getLatestPositions() throws StorageException { - List<Position> positions = new LinkedList<>(); - List<Device> devices = storage.getObjects(Device.class, new Request(new Columns.Include("positionId"))); - for (Device device : devices) { - positions.addAll(storage.getObjects(Position.class, new Request( - new Columns.All(), - new Condition.Equals("id", "id", device.getPositionId())))); - } - return positions; - } - - public Server getServer() throws StorageException { - return storage.getObject(Server.class, new Request(new Columns.All())); - } - - public Collection<Event> getEvents(long deviceId, Date from, Date to) throws StorageException { - return storage.getObjects(Event.class, new Request( - new Columns.All(), - new Condition.And( - new Condition.Equals("deviceId", "deviceId", deviceId), - new Condition.Between("eventTime", "from", from, "to", to)), - new Order("eventTime"))); - } - - public Collection<Statistics> getStatistics(Date from, Date to) throws StorageException { - return storage.getObjects(Statistics.class, new Request( - new Columns.All(), - new Condition.Between("captureTime", "from", from, "to", to), - new Order("captureTime"))); - } - - public Collection<Permission> getPermissions(Class<? extends BaseModel> owner, Class<? extends BaseModel> property) - throws StorageException, ClassNotFoundException { - return storage.getPermissions(owner, property); - } - - public void linkObject(Class<?> owner, long ownerId, Class<?> property, long propertyId, boolean link) - throws StorageException { - if (link) { - storage.addPermission(new Permission(owner, ownerId, property, propertyId)); - } else { - storage.removePermission(new Permission(owner, ownerId, property, propertyId)); - } - } - - public <T extends BaseModel> T getObject(Class<T> clazz, long entityId) throws StorageException { - return storage.getObject(clazz, new Request( - new Columns.All(), - new Condition.Equals("id", "id", entityId))); - } - - public <T extends BaseModel> Collection<T> getObjects(Class<T> clazz) throws StorageException { - return storage.getObjects(clazz, new Request(new Columns.All())); - } - - public void addObject(BaseModel entity) throws StorageException { - entity.setId(storage.addObject(entity, new Request(new Columns.Exclude("id")))); - } - - public void updateObject(BaseModel entity) throws StorageException { - storage.updateObject(entity, new Request( - new Columns.Exclude("id"), - new Condition.Equals("id", "id"))); - } - - public void removeObject(Class<? extends BaseModel> clazz, long entityId) throws StorageException { - storage.removeObject(clazz, new Request(new Condition.Equals("id", "id", entityId))); - } - -} diff --git a/src/main/java/org/traccar/database/DeviceLookupService.java b/src/main/java/org/traccar/database/DeviceLookupService.java new file mode 100644 index 000000000..6ec6841a1 --- /dev/null +++ b/src/main/java/org/traccar/database/DeviceLookupService.java @@ -0,0 +1,141 @@ +/* + * 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.database; + +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.model.Device; +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 jakarta.inject.Inject; +import jakarta.inject.Singleton; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +@Singleton +public class DeviceLookupService { + + private static final Logger LOGGER = LoggerFactory.getLogger(DeviceLookupService.class); + + private static final long INFO_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(60); + private static final long THROTTLE_MIN_MS = TimeUnit.MINUTES.toMillis(1); + private static final long THROTTLE_MAX_MS = TimeUnit.MINUTES.toMillis(30); + + private final Storage storage; + private final Timer timer; + + private final boolean throttlingEnabled; + + private static class IdentifierInfo { + private long lastQuery; + private long delay; + private Timeout timeout; + } + + private final class IdentifierTask implements TimerTask { + private final String uniqueId; + + private IdentifierTask(String uniqueId) { + this.uniqueId = uniqueId; + } + + @Override + public void run(Timeout timeout) { + LOGGER.debug("Device lookup expired {}", uniqueId); + synchronized (DeviceLookupService.this) { + identifierMap.remove(uniqueId); + } + } + } + + private final Map<String, IdentifierInfo> identifierMap = new ConcurrentHashMap<>(); + + @Inject + public DeviceLookupService(Config config, Storage storage, Timer timer) { + this.storage = storage; + this.timer = timer; + throttlingEnabled = config.getBoolean(Keys.DATABASE_THROTTLE_UNKNOWN); + } + + private synchronized boolean isThrottled(String uniqueId) { + if (throttlingEnabled) { + IdentifierInfo info = identifierMap.get(uniqueId); + return info != null && System.currentTimeMillis() < info.lastQuery + info.delay; + } else { + return false; + } + } + + private synchronized void lookupSucceeded(String uniqueId) { + if (throttlingEnabled) { + IdentifierInfo info = identifierMap.remove(uniqueId); + if (info != null) { + info.timeout.cancel(); + } + } + } + + private synchronized void lookupFailed(String uniqueId) { + if (throttlingEnabled) { + IdentifierInfo info = identifierMap.get(uniqueId); + if (info != null) { + info.timeout.cancel(); + info.delay = Math.min(info.delay * 2, THROTTLE_MAX_MS); + } else { + info = new IdentifierInfo(); + identifierMap.put(uniqueId, info); + info.delay = THROTTLE_MIN_MS; + } + info.lastQuery = System.currentTimeMillis(); + info.timeout = timer.newTimeout(new IdentifierTask(uniqueId), INFO_TIMEOUT_MS, TimeUnit.MILLISECONDS); + LOGGER.debug("Device lookup {} throttled for {} ms", uniqueId, info.delay); + } + } + + public Device lookup(String[] uniqueIds) { + Device device = null; + try { + for (String uniqueId : uniqueIds) { + if (!isThrottled(uniqueId)) { + device = storage.getObject(Device.class, new Request( + new Columns.All(), new Condition.Equals("uniqueId", uniqueId))); + if (device != null) { + lookupSucceeded(uniqueId); + break; + } else { + lookupFailed(uniqueId); + } + } else { + LOGGER.debug("Device lookup throttled {}", uniqueId); + } + } + } catch (StorageException e) { + LOGGER.warn("Find device error", e); + } + return device; + } + +} diff --git a/src/main/java/org/traccar/database/DeviceManager.java b/src/main/java/org/traccar/database/DeviceManager.java deleted file mode 100644 index 40591e869..000000000 --- a/src/main/java/org/traccar/database/DeviceManager.java +++ /dev/null @@ -1,471 +0,0 @@ -/* - * Copyright 2016 - 2020 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.database; - -import java.sql.SQLException; -import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicLong; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.config.Config; -import org.traccar.Context; -import org.traccar.config.Keys; -import org.traccar.model.Command; -import org.traccar.model.Device; -import org.traccar.model.DeviceState; -import org.traccar.model.DeviceAccumulators; -import org.traccar.model.Group; -import org.traccar.model.Position; -import org.traccar.model.Server; -import org.traccar.storage.StorageException; - -public class DeviceManager extends BaseObjectManager<Device> implements IdentityManager, ManagableObjects { - - private static final Logger LOGGER = LoggerFactory.getLogger(DeviceManager.class); - - private final Config config; - private final long dataRefreshDelay; - - private Map<String, Device> devicesByUniqueId; - private Map<String, Device> devicesByPhone; - private final AtomicLong devicesLastUpdate = new AtomicLong(); - - private final Map<Long, Position> positions = new ConcurrentHashMap<>(); - - private final Map<Long, DeviceState> deviceStates = new ConcurrentHashMap<>(); - - public DeviceManager(DataManager dataManager) { - super(dataManager, Device.class); - this.config = Context.getConfig(); - try { - writeLock(); - if (devicesByPhone == null) { - devicesByPhone = new ConcurrentHashMap<>(); - } - if (devicesByUniqueId == null) { - devicesByUniqueId = new ConcurrentHashMap<>(); - } - } finally { - writeUnlock(); - } - dataRefreshDelay = config.getLong(Keys.DATABASE_REFRESH_DELAY) * 1000; - refreshLastPositions(); - } - - @Override - public long 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(); - Context.getPermissionsManager().refreshAllExtendedPermissions(); - } - - return device.getId(); - } catch (StorageException e) { - LOGGER.warn("Automatic device registration error", e); - return 0; - } - } - - public void updateDeviceCache(boolean force) { - long lastUpdate = devicesLastUpdate.get(); - if ((force || System.currentTimeMillis() - lastUpdate > dataRefreshDelay) - && devicesLastUpdate.compareAndSet(lastUpdate, System.currentTimeMillis())) { - refreshItems(); - } - } - - @Override - public Device getByUniqueId(String uniqueId) throws SQLException { - boolean forceUpdate; - try { - readLock(); - forceUpdate = !devicesByUniqueId.containsKey(uniqueId) && !config.getBoolean(Keys.DATABASE_IGNORE_UNKNOWN); - } finally { - readUnlock(); - } - updateDeviceCache(forceUpdate); - try { - readLock(); - return devicesByUniqueId.get(uniqueId); - } finally { - readUnlock(); - } - } - - @Override - public String getDevicePassword(long id, String protocol, String defaultPassword) { - - String password = lookupAttributeString(id, Command.KEY_DEVICE_PASSWORD, null, false, false); - if (password != null) { - return password; - } - - if (protocol != null) { - password = Context.getConfig().getString(Keys.PROTOCOL_DEVICE_PASSWORD.withPrefix(protocol)); - if (password != null) { - return password; - } - } - - return defaultPassword; - } - - @Override - public Set<Long> getAllItems() { - Set<Long> result = super.getAllItems(); - if (result.isEmpty()) { - updateDeviceCache(true); - result = super.getAllItems(); - } - return result; - } - - public Collection<Device> getAllDevices() { - return getItems(getAllItems()); - } - - public Set<Long> getAllUserItems(long userId) { - return Context.getPermissionsManager().getDevicePermissions(userId); - } - - @Override - public Set<Long> getUserItems(long userId) { - if (Context.getPermissionsManager() != null) { - Set<Long> result = new HashSet<>(); - for (long deviceId : Context.getPermissionsManager().getDevicePermissions(userId)) { - Device device = getById(deviceId); - if (device != null && !device.getDisabled()) { - result.add(deviceId); - } - } - return result; - } else { - return new HashSet<>(); - } - } - - public Set<Long> getAllManagedItems(long userId) { - Set<Long> result = new HashSet<>(getAllUserItems(userId)); - for (long managedUserId : Context.getUsersManager().getUserItems(userId)) { - result.addAll(getAllUserItems(managedUserId)); - } - return result; - } - - @Override - public Set<Long> getManagedItems(long userId) { - Set<Long> result = new HashSet<>(getUserItems(userId)); - for (long managedUserId : Context.getUsersManager().getUserItems(userId)) { - result.addAll(getUserItems(managedUserId)); - } - return result; - } - - private void addByUniqueId(Device device) { - try { - writeLock(); - if (devicesByUniqueId == null) { - devicesByUniqueId = new ConcurrentHashMap<>(); - } - devicesByUniqueId.put(device.getUniqueId(), device); - } finally { - writeUnlock(); - } - } - - private void removeByUniqueId(String deviceUniqueId) { - try { - writeLock(); - if (devicesByUniqueId != null) { - devicesByUniqueId.remove(deviceUniqueId); - } - } finally { - writeUnlock(); - } - } - - private void addByPhone(Device device) { - try { - writeLock(); - if (devicesByPhone == null) { - devicesByPhone = new ConcurrentHashMap<>(); - } - devicesByPhone.put(device.getPhone(), device); - } finally { - writeUnlock(); - } - } - - private void removeByPhone(String phone) { - if (phone == null || phone.isEmpty()) { - return; - } - try { - writeLock(); - if (devicesByPhone != null) { - devicesByPhone.remove(phone); - } - } finally { - writeUnlock(); - } - } - - @Override - protected void addNewItem(Device device) { - super.addNewItem(device); - addByUniqueId(device); - if (device.getPhone() != null && !device.getPhone().isEmpty()) { - addByPhone(device); - } - if (Context.getGeofenceManager() != null) { - Position lastPosition = getLastPosition(device.getId()); - if (lastPosition != null) { - device.setGeofenceIds(Context.getGeofenceManager().getCurrentDeviceGeofences(lastPosition)); - } - } - } - - @Override - protected void updateCachedItem(Device device) { - Device cachedDevice = getById(device.getId()); - cachedDevice.setName(device.getName()); - cachedDevice.setGroupId(device.getGroupId()); - cachedDevice.setCategory(device.getCategory()); - cachedDevice.setContact(device.getContact()); - cachedDevice.setModel(device.getModel()); - cachedDevice.setDisabled(device.getDisabled()); - cachedDevice.setAttributes(device.getAttributes()); - if (!device.getUniqueId().equals(cachedDevice.getUniqueId())) { - removeByUniqueId(cachedDevice.getUniqueId()); - cachedDevice.setUniqueId(device.getUniqueId()); - addByUniqueId(cachedDevice); - } - if (device.getPhone() != null && !device.getPhone().isEmpty() - && !device.getPhone().equals(cachedDevice.getPhone())) { - String phone = cachedDevice.getPhone(); - removeByPhone(phone); - cachedDevice.setPhone(device.getPhone()); - addByPhone(cachedDevice); - } - } - - @Override - protected void removeCachedItem(long deviceId) { - Device cachedDevice = getById(deviceId); - if (cachedDevice != null) { - String deviceUniqueId = cachedDevice.getUniqueId(); - String phone = cachedDevice.getPhone(); - super.removeCachedItem(deviceId); - removeByUniqueId(deviceUniqueId); - removeByPhone(phone); - } - positions.remove(deviceId); - } - - public void updateDeviceStatus(Device device) throws StorageException { - getDataManager().updateDeviceStatus(device); - Device cachedDevice = getById(device.getId()); - if (cachedDevice != null) { - cachedDevice.setStatus(device.getStatus()); - } - } - - private void refreshLastPositions() { - if (getDataManager() != null) { - try { - for (Position position : getDataManager().getLatestPositions()) { - positions.put(position.getDeviceId(), position); - } - } catch (StorageException error) { - LOGGER.warn("Load latest positions error", error); - } - } - } - - public boolean isLatestPosition(Position position) { - Position lastPosition = getLastPosition(position.getDeviceId()); - return lastPosition == null || position.getFixTime().compareTo(lastPosition.getFixTime()) >= 0; - } - - public void updateLatestPosition(Position position) throws StorageException { - - if (isLatestPosition(position)) { - - getDataManager().updateLatestPosition(position); - - Device device = getById(position.getDeviceId()); - if (device != null) { - device.setPositionId(position.getId()); - } - - positions.put(position.getDeviceId(), position); - - if (Context.getConnectionManager() != null) { - Context.getConnectionManager().updatePosition(position); - } - } - } - - @Override - public Position getLastPosition(long deviceId) { - return positions.get(deviceId); - } - - public Collection<Position> getInitialState(long userId) { - - List<Position> result = new LinkedList<>(); - - if (Context.getPermissionsManager() != null) { - for (long deviceId : Context.getPermissionsManager().getUserAdmin(userId) - ? getAllUserItems(userId) : getUserItems(userId)) { - if (positions.containsKey(deviceId)) { - result.add(positions.get(deviceId)); - } - } - } - - return result; - } - - @Override - public boolean lookupAttributeBoolean( - long deviceId, String attributeName, boolean defaultValue, boolean lookupServer, boolean lookupConfig) { - Object result = lookupAttribute(deviceId, attributeName, lookupServer, lookupConfig); - if (result != null) { - return result instanceof String ? Boolean.parseBoolean((String) result) : (Boolean) result; - } - return defaultValue; - } - - @Override - public String lookupAttributeString( - long deviceId, String attributeName, String defaultValue, boolean lookupServer, boolean lookupConfig) { - Object result = lookupAttribute(deviceId, attributeName, lookupServer, lookupConfig); - 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); - if (device != null) { - result = device.getAttributes().get(attributeName); - if (result == null) { - long groupId = device.getGroupId(); - while (groupId != 0) { - Group group = Context.getGroupsManager().getById(groupId); - if (group != null) { - result = group.getAttributes().get(attributeName); - if (result != null) { - break; - } - groupId = group.getGroupId(); - } else { - groupId = 0; - } - } - } - if (result == null && lookupServer) { - Server server = Context.getPermissionsManager().getServer(); - result = server.getAttributes().get(attributeName); - } - if (result == null && lookupConfig) { - result = Context.getConfig().getString(attributeName); - } - } - return result; - } - - public void resetDeviceAccumulators(DeviceAccumulators deviceAccumulators) throws StorageException { - Position last = positions.get(deviceAccumulators.getDeviceId()); - if (last != null) { - if (deviceAccumulators.getTotalDistance() != null) { - last.getAttributes().put(Position.KEY_TOTAL_DISTANCE, deviceAccumulators.getTotalDistance()); - } - if (deviceAccumulators.getHours() != null) { - last.getAttributes().put(Position.KEY_HOURS, deviceAccumulators.getHours()); - } - getDataManager().addObject(last); - updateLatestPosition(last); - } else { - throw new IllegalArgumentException(); - } - } - - public DeviceState getDeviceState(long deviceId) { - DeviceState deviceState = deviceStates.get(deviceId); - if (deviceState == null) { - deviceState = new DeviceState(); - deviceStates.put(deviceId, deviceState); - } - return deviceState; - } - - public void setDeviceState(long deviceId, DeviceState deviceState) { - deviceStates.put(deviceId, deviceState); - } - -} diff --git a/src/main/java/org/traccar/database/DriversManager.java b/src/main/java/org/traccar/database/DriversManager.java deleted file mode 100644 index d111cd643..000000000 --- a/src/main/java/org/traccar/database/DriversManager.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) - * Copyright 2017 Andrey Kunitsyn (andrey@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.database; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.traccar.model.Driver; - -public class DriversManager extends ExtendedObjectManager<Driver> { - - private Map<String, Driver> driversByUniqueId; - - public DriversManager(DataManager dataManager) { - super(dataManager, Driver.class); - try { - writeLock(); - if (driversByUniqueId == null) { - driversByUniqueId = new ConcurrentHashMap<>(); - } - } finally { - writeUnlock(); - } - } - - private void addByUniqueId(Driver driver) { - try { - writeLock(); - if (driversByUniqueId == null) { - driversByUniqueId = new ConcurrentHashMap<>(); - } - driversByUniqueId.put(driver.getUniqueId(), driver); - } finally { - writeUnlock(); - } - } - - private void removeByUniqueId(String driverUniqueId) { - try { - writeLock(); - if (driversByUniqueId == null) { - driversByUniqueId = new ConcurrentHashMap<>(); - } - driversByUniqueId.remove(driverUniqueId); - } finally { - writeUnlock(); - } - } - - @Override - protected void addNewItem(Driver driver) { - super.addNewItem(driver); - addByUniqueId(driver); - } - - @Override - protected void updateCachedItem(Driver driver) { - Driver cachedDriver = getById(driver.getId()); - cachedDriver.setName(driver.getName()); - if (!driver.getUniqueId().equals(cachedDriver.getUniqueId())) { - removeByUniqueId(cachedDriver.getUniqueId()); - cachedDriver.setUniqueId(driver.getUniqueId()); - addByUniqueId(cachedDriver); - } - cachedDriver.setAttributes(driver.getAttributes()); - } - - @Override - protected void removeCachedItem(long driverId) { - Driver cachedDriver = getById(driverId); - if (cachedDriver != null) { - String driverUniqueId = cachedDriver.getUniqueId(); - super.removeCachedItem(driverId); - removeByUniqueId(driverUniqueId); - } - } - - public Driver getDriverByUniqueId(String uniqueId) { - try { - readLock(); - return driversByUniqueId.get(uniqueId); - } finally { - readUnlock(); - } - } -} diff --git a/src/main/java/org/traccar/database/ExtendedObjectManager.java b/src/main/java/org/traccar/database/ExtendedObjectManager.java deleted file mode 100644 index 006ed47b2..000000000 --- a/src/main/java/org/traccar/database/ExtendedObjectManager.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) - * Copyright 2017 Andrey Kunitsyn (andrey@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.database; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.Context; -import org.traccar.model.Device; -import org.traccar.model.Group; -import org.traccar.model.Permission; -import org.traccar.model.BaseModel; -import org.traccar.storage.StorageException; - -public abstract class ExtendedObjectManager<T extends BaseModel> extends SimpleObjectManager<T> { - - private static final Logger LOGGER = LoggerFactory.getLogger(ExtendedObjectManager.class); - - private final Map<Long, Set<Long>> deviceItems = new ConcurrentHashMap<>(); - private final Map<Long, Set<Long>> deviceItemsWithGroups = new ConcurrentHashMap<>(); - private final Map<Long, Set<Long>> groupItems = new ConcurrentHashMap<>(); - - protected ExtendedObjectManager(DataManager dataManager, Class<T> baseClass) { - super(dataManager, baseClass); - refreshExtendedPermissions(); - } - - public final Set<Long> getGroupItems(long groupId) { - try { - readLock(); - Set<Long> result = groupItems.get(groupId); - if (result != null) { - return new HashSet<>(result); - } else { - return new HashSet<>(); - } - } finally { - readUnlock(); - } - } - - public final Set<Long> getDeviceItems(long deviceId) { - try { - readLock(); - Set<Long> result = deviceItems.get(deviceId); - if (result != null) { - return new HashSet<>(result); - } else { - return new HashSet<>(); - } - } finally { - readUnlock(); - } - } - - public Set<Long> getAllDeviceItems(long deviceId) { - try { - readLock(); - Set<Long> result = deviceItemsWithGroups.get(deviceId); - if (result != null) { - return new HashSet<>(result); - } else { - return new HashSet<>(); - } - } finally { - readUnlock(); - } - } - - @Override - public void removeItem(long itemId) throws StorageException { - super.removeItem(itemId); - refreshExtendedPermissions(); - } - - public void refreshExtendedPermissions() { - if (getDataManager() != null) { - try { - Collection<Permission> databaseGroupPermissions = - getDataManager().getPermissions(Group.class, getBaseClass()); - - Collection<Permission> databaseDevicePermissions = - getDataManager().getPermissions(Device.class, getBaseClass()); - - writeLock(); - - groupItems.clear(); - deviceItems.clear(); - deviceItemsWithGroups.clear(); - - for (Permission groupPermission : databaseGroupPermissions) { - groupItems - .computeIfAbsent(groupPermission.getOwnerId(), key -> new HashSet<>()) - .add(groupPermission.getPropertyId()); - } - - for (Permission devicePermission : databaseDevicePermissions) { - deviceItems - .computeIfAbsent(devicePermission.getOwnerId(), key -> new HashSet<>()) - .add(devicePermission.getPropertyId()); - deviceItemsWithGroups - .computeIfAbsent(devicePermission.getOwnerId(), key -> new HashSet<>()) - .add(devicePermission.getPropertyId()); - } - - for (Device device : Context.getDeviceManager().getAllDevices()) { - long groupId = device.getGroupId(); - while (groupId > 0) { - deviceItemsWithGroups - .computeIfAbsent(device.getId(), key -> new HashSet<>()) - .addAll(groupItems.getOrDefault(groupId, new HashSet<>())); - Group group = Context.getGroupsManager().getById(groupId); - groupId = group != null ? group.getGroupId() : 0; - } - } - - } catch (StorageException | ClassNotFoundException error) { - LOGGER.warn("Refresh permissions error", error); - } finally { - writeUnlock(); - } - } - } -} diff --git a/src/main/java/org/traccar/database/GeofenceManager.java b/src/main/java/org/traccar/database/GeofenceManager.java deleted file mode 100644 index a32847cf9..000000000 --- a/src/main/java/org/traccar/database/GeofenceManager.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2016 - 2017 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.database; - -import java.util.ArrayList; -import java.util.List; - -import org.traccar.Context; -import org.traccar.model.Device; -import org.traccar.model.Geofence; -import org.traccar.model.Position; - -public class GeofenceManager extends ExtendedObjectManager<Geofence> { - - public GeofenceManager(DataManager dataManager) { - super(dataManager, Geofence.class); - } - - @Override - public final void refreshExtendedPermissions() { - super.refreshExtendedPermissions(); - recalculateDevicesGeofences(); - } - - public List<Long> getCurrentDeviceGeofences(Position position) { - List<Long> result = new ArrayList<>(); - for (long geofenceId : getAllDeviceItems(position.getDeviceId())) { - Geofence geofence = getById(geofenceId); - if (geofence != null && geofence.getGeometry() - .containsPoint(position.getLatitude(), position.getLongitude())) { - result.add(geofenceId); - } - } - return result; - } - - public void recalculateDevicesGeofences() { - for (Device device : Context.getDeviceManager().getAllDevices()) { - List<Long> deviceGeofenceIds = device.getGeofenceIds(); - if (deviceGeofenceIds == null) { - deviceGeofenceIds = new ArrayList<>(); - } else { - deviceGeofenceIds.clear(); - } - Position lastPosition = Context.getIdentityManager().getLastPosition(device.getId()); - if (lastPosition != null && getAllDeviceItems(device.getId()) != null) { - deviceGeofenceIds.addAll(getCurrentDeviceGeofences(lastPosition)); - } - device.setGeofenceIds(deviceGeofenceIds); - } - } - -} diff --git a/src/main/java/org/traccar/database/GroupsManager.java b/src/main/java/org/traccar/database/GroupsManager.java deleted file mode 100644 index dafddc0cc..000000000 --- a/src/main/java/org/traccar/database/GroupsManager.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) - * Copyright 2017 Andrey Kunitsyn (andrey@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.database; - -import java.util.HashSet; -import java.util.Set; - -import org.traccar.Context; -import org.traccar.model.Group; -import org.traccar.storage.StorageException; - -public class GroupsManager extends BaseObjectManager<Group> implements ManagableObjects { - - public GroupsManager(DataManager dataManager) { - super(dataManager, Group.class); - } - - private void checkGroupCycles(Group group) { - Set<Long> groups = new HashSet<>(); - while (group != null) { - if (groups.contains(group.getId())) { - throw new IllegalArgumentException("Cycle in group hierarchy"); - } - groups.add(group.getId()); - group = getById(group.getGroupId()); - } - } - - @Override - public Set<Long> getAllItems() { - Set<Long> result = super.getAllItems(); - if (result.isEmpty()) { - refreshItems(); - result = super.getAllItems(); - } - return result; - } - - @Override - protected void addNewItem(Group group) { - checkGroupCycles(group); - super.addNewItem(group); - } - - @Override - public void updateItem(Group group) throws StorageException { - checkGroupCycles(group); - super.updateItem(group); - } - - @Override - public Set<Long> getUserItems(long userId) { - if (Context.getPermissionsManager() != null) { - return Context.getPermissionsManager().getGroupPermissions(userId); - } else { - return new HashSet<>(); - } - } - - @Override - public Set<Long> getManagedItems(long userId) { - Set<Long> result = getUserItems(userId); - for (long managedUserId : Context.getUsersManager().getUserItems(userId)) { - result.addAll(getUserItems(managedUserId)); - } - return result; - } - -} diff --git a/src/main/java/org/traccar/database/IdentityManager.java b/src/main/java/org/traccar/database/IdentityManager.java deleted file mode 100644 index af6a6ce71..000000000 --- a/src/main/java/org/traccar/database/IdentityManager.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2015 - 2019 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.database; - -import org.traccar.model.Device; -import org.traccar.model.Position; - -public interface IdentityManager { - - long addUnknownDevice(String uniqueId); - - Device getById(long id); - - Device getByUniqueId(String uniqueId) throws Exception; - - String getDevicePassword(long id, String protocol, String defaultPassword); - - Position getLastPosition(long deviceId); - - boolean isLatestPosition(Position position); - - boolean lookupAttributeBoolean( - long deviceId, String attributeName, boolean defaultValue, boolean lookupServer, boolean lookupConfig); - - 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/database/LdapProvider.java b/src/main/java/org/traccar/database/LdapProvider.java index d659a11a1..d517294b8 100644 --- a/src/main/java/org/traccar/database/LdapProvider.java +++ b/src/main/java/org/traccar/database/LdapProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2020 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. diff --git a/src/main/java/org/traccar/database/MailManager.java b/src/main/java/org/traccar/database/MailManager.java deleted file mode 100644 index d94f55cda..000000000 --- a/src/main/java/org/traccar/database/MailManager.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org) - * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@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.database; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.Context; -import org.traccar.Main; -import org.traccar.model.User; -import org.traccar.notification.PropertiesProvider; - -import javax.mail.BodyPart; -import javax.mail.Message; -import javax.mail.MessagingException; -import javax.mail.Multipart; -import javax.mail.Session; -import javax.mail.Transport; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeBodyPart; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeMultipart; -import java.util.Date; -import java.util.Properties; - -public final class MailManager { - - private static final Logger LOGGER = LoggerFactory.getLogger(MailManager.class); - - private static Properties getProperties(PropertiesProvider provider) { - Properties properties = new Properties(); - String host = provider.getString("mail.smtp.host"); - if (host != null) { - properties.put("mail.transport.protocol", provider.getString("mail.transport.protocol", "smtp")); - properties.put("mail.smtp.host", host); - properties.put("mail.smtp.port", String.valueOf(provider.getInteger("mail.smtp.port", 25))); - - Boolean starttlsEnable = provider.getBoolean("mail.smtp.starttls.enable"); - if (starttlsEnable != null) { - properties.put("mail.smtp.starttls.enable", String.valueOf(starttlsEnable)); - } - Boolean starttlsRequired = provider.getBoolean("mail.smtp.starttls.required"); - if (starttlsRequired != null) { - properties.put("mail.smtp.starttls.required", String.valueOf(starttlsRequired)); - } - - Boolean sslEnable = provider.getBoolean("mail.smtp.ssl.enable"); - if (sslEnable != null) { - properties.put("mail.smtp.ssl.enable", String.valueOf(sslEnable)); - } - String sslTrust = provider.getString("mail.smtp.ssl.trust"); - if (sslTrust != null) { - properties.put("mail.smtp.ssl.trust", sslTrust); - } - - String sslProtocols = provider.getString("mail.smtp.ssl.protocols"); - if (sslProtocols != null) { - properties.put("mail.smtp.ssl.protocols", sslProtocols); - } - - String username = provider.getString("mail.smtp.username"); - if (username != null) { - properties.put("mail.smtp.username", username); - } - String password = provider.getString("mail.smtp.password"); - if (password != null) { - properties.put("mail.smtp.password", password); - } - String from = provider.getString("mail.smtp.from"); - if (from != null) { - properties.put("mail.smtp.from", from); - } - } - return properties; - } - - public boolean getEmailEnabled() { - return Context.getConfig().hasKey("mail.smtp.host"); - } - - public void sendMessage( - long userId, String subject, String body) throws MessagingException { - sendMessage(userId, subject, body, null); - } - - public void sendMessage( - long userId, String subject, String body, MimeBodyPart attachment) throws MessagingException { - User user = Context.getPermissionsManager().getUser(userId); - - Properties properties = null; - if (!Context.getConfig().getBoolean("mail.smtp.ignoreUserConfig")) { - properties = getProperties(new PropertiesProvider(user)); - } - if (properties == null || !properties.containsKey("mail.smtp.host")) { - properties = getProperties(new PropertiesProvider(Context.getConfig())); - } - if (!properties.containsKey("mail.smtp.host")) { - LOGGER.warn("No SMTP configuration found"); - return; - } - - Session session = Session.getInstance(properties); - - MimeMessage message = new MimeMessage(session); - - String from = properties.getProperty("mail.smtp.from"); - if (from != null) { - message.setFrom(new InternetAddress(from)); - } - - message.addRecipient(Message.RecipientType.TO, new InternetAddress(user.getEmail())); - message.setSubject(subject); - message.setSentDate(new Date()); - - if (attachment != null) { - Multipart multipart = new MimeMultipart(); - - BodyPart messageBodyPart = new MimeBodyPart(); - messageBodyPart.setContent(body, "text/html; charset=utf-8"); - multipart.addBodyPart(messageBodyPart); - multipart.addBodyPart(attachment); - - message.setContent(multipart); - } else { - message.setContent(body, "text/html; charset=utf-8"); - } - - try (Transport transport = session.getTransport()) { - Main.getInjector().getInstance(StatisticsManager.class).registerMail(); - transport.connect( - properties.getProperty("mail.smtp.host"), - properties.getProperty("mail.smtp.username"), - properties.getProperty("mail.smtp.password")); - transport.sendMessage(message, message.getAllRecipients()); - } - } - -} diff --git a/src/main/java/org/traccar/database/MaintenancesManager.java b/src/main/java/org/traccar/database/MaintenancesManager.java deleted file mode 100644 index 4e266cb78..000000000 --- a/src/main/java/org/traccar/database/MaintenancesManager.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2018 Anton Tananaev (anton@traccar.org) - * Copyright 2018 Andrey Kunitsyn (andrey@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.database; - -import org.traccar.model.Maintenance; - -public class MaintenancesManager extends ExtendedObjectManager<Maintenance> { - - public MaintenancesManager(DataManager dataManager) { - super(dataManager, Maintenance.class); - } - -} diff --git a/src/main/java/org/traccar/database/ManagableObjects.java b/src/main/java/org/traccar/database/ManagableObjects.java deleted file mode 100644 index ec9549493..000000000 --- a/src/main/java/org/traccar/database/ManagableObjects.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2017 Anton Tananaev (anton@traccar.org) - * Copyright 2017 Andrey Kunitsyn (andrey@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.database; - -import java.util.Set; - -public interface ManagableObjects { - - Set<Long> getUserItems(long userId); - - Set<Long> getManagedItems(long userId); - -} diff --git a/src/main/java/org/traccar/database/MediaManager.java b/src/main/java/org/traccar/database/MediaManager.java index edade5766..2f2369c96 100644 --- a/src/main/java/org/traccar/database/MediaManager.java +++ b/src/main/java/org/traccar/database/MediaManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2018 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. @@ -18,10 +18,15 @@ package org.traccar.database; import io.netty.buffer.ByteBuf; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.Files; @@ -30,14 +35,16 @@ import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.Date; +@Singleton public class MediaManager { private static final Logger LOGGER = LoggerFactory.getLogger(MediaManager.class); - private String path; + private final String path; - public MediaManager(String path) { - this.path = path; + @Inject + public MediaManager(Config config) { + this.path = config.getString(Keys.MEDIA_PATH); } private File createFile(String uniqueId, String name) throws IOException { @@ -49,6 +56,10 @@ public class MediaManager { return filePath.toFile(); } + public OutputStream createFileStream(String uniqueId, String name, String extension) throws IOException { + return new FileOutputStream(createFile(uniqueId, name + "." + extension)); + } + public String writeFile(String uniqueId, ByteBuf buf, String extension) { if (path != null) { int size = buf.readableBytes(); diff --git a/src/main/java/org/traccar/database/NotificationManager.java b/src/main/java/org/traccar/database/NotificationManager.java index f358b1d4d..3a57788fb 100644 --- a/src/main/java/org/traccar/database/NotificationManager.java +++ b/src/main/java/org/traccar/database/NotificationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2016 - 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,128 +16,141 @@ */ package org.traccar.database; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.Arrays; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.traccar.Context; +import org.traccar.config.Config; import org.traccar.config.Keys; +import org.traccar.forward.EventData; +import org.traccar.forward.EventForwarder; +import org.traccar.geocoder.Geocoder; import org.traccar.model.Calendar; +import org.traccar.model.Device; import org.traccar.model.Event; +import org.traccar.model.Geofence; +import org.traccar.model.Maintenance; import org.traccar.model.Notification; import org.traccar.model.Position; -import org.traccar.model.Typed; +import org.traccar.notification.MessageException; +import org.traccar.notification.NotificatorManager; +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.Request; + +import jakarta.annotation.Nullable; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import java.util.Arrays; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; -public class NotificationManager extends ExtendedObjectManager<Notification> { +@Singleton +public class NotificationManager { private static final Logger LOGGER = LoggerFactory.getLogger(NotificationManager.class); - private final boolean geocodeOnRequest; + private final Storage storage; + private final CacheManager cacheManager; + private final EventForwarder eventForwarder; + private final NotificatorManager notificatorManager; + private final Geocoder geocoder; - public NotificationManager(DataManager dataManager) { - super(dataManager, Notification.class); - geocodeOnRequest = Context.getConfig().getBoolean(Keys.GEOCODER_ON_REQUEST); - } + private final boolean geocodeOnRequest; - private Set<Long> getEffectiveNotifications(long userId, long deviceId, Date time) { - Set<Long> result = new HashSet<>(); - Set<Long> deviceNotifications = getAllDeviceItems(deviceId); - for (long itemId : getUserItems(userId)) { - if (getById(itemId).getAlways() || deviceNotifications.contains(itemId)) { - long calendarId = getById(itemId).getCalendarId(); - Calendar calendar = calendarId != 0 ? Context.getCalendarManager().getById(calendarId) : null; - if (calendar == null || calendar.checkMoment(time)) { - result.add(itemId); - } - } - } - return result; + @Inject + public NotificationManager( + Config config, Storage storage, CacheManager cacheManager, @Nullable EventForwarder eventForwarder, + NotificatorManager notificatorManager, @Nullable Geocoder geocoder) { + this.storage = storage; + this.cacheManager = cacheManager; + this.eventForwarder = eventForwarder; + this.notificatorManager = notificatorManager; + this.geocoder = geocoder; + geocodeOnRequest = config.getBoolean(Keys.GEOCODER_ON_REQUEST); } - public void updateEvent(Event event, Position position) { + private void updateEvent(Event event, Position position) { try { - getDataManager().addObject(event); + event.setId(storage.addObject(event, new Request(new Columns.Exclude("id")))); } catch (StorageException error) { LOGGER.warn("Event save error", error); } - long deviceId = event.getDeviceId(); - Set<Long> users = Context.getPermissionsManager().getDeviceUsers(deviceId); - Set<Long> usersToForward = null; - if (Context.getEventForwarder() != null) { - usersToForward = new HashSet<>(); - } - for (long userId : users) { - if ((event.getGeofenceId() == 0 - || Context.getGeofenceManager().checkItemPermission(userId, event.getGeofenceId())) - && (event.getMaintenanceId() == 0 - || Context.getMaintenancesManager().checkItemPermission(userId, event.getMaintenanceId()))) { - if (usersToForward != null) { - usersToForward.add(userId); - } - final Set<String> notificators = new HashSet<>(); - for (long notificationId : getEffectiveNotifications(userId, deviceId, event.getEventTime())) { - Notification notification = getById(notificationId); - if (getById(notificationId).getType().equals(event.getType())) { - boolean filter = false; - if (event.getType().equals(Event.TYPE_ALARM)) { - String alarmsAttribute = notification.getString("alarms"); - if (alarmsAttribute == null) { - filter = true; - } else { - List<String> alarms = Arrays.asList(alarmsAttribute.split(",")); - filter = !alarms.contains(event.getString(Position.KEY_ALARM)); - } - } - if (!filter) { - notificators.addAll(notification.getNotificatorsTypes()); + var notifications = cacheManager.getDeviceObjects(event.getDeviceId(), Notification.class).stream() + .filter(notification -> notification.getType().equals(event.getType())) + .filter(notification -> { + if (event.getType().equals(Event.TYPE_ALARM)) { + String alarmsAttribute = notification.getString("alarms"); + if (alarmsAttribute != null) { + return Arrays.asList(alarmsAttribute.split(",")) + .contains(event.getString(Position.KEY_ALARM)); } + return false; } - } - - if (position != null && position.getAddress() == null - && geocodeOnRequest && Context.getGeocoder() != null) { - position.setAddress(Context.getGeocoder() - .getAddress(position.getLatitude(), position.getLongitude(), null)); - } + return true; + }) + .filter(notification -> { + long calendarId = notification.getCalendarId(); + Calendar calendar = calendarId != 0 ? cacheManager.getObject(Calendar.class, calendarId) : null; + return calendar == null || calendar.checkMoment(event.getEventTime()); + }) + .collect(Collectors.toUnmodifiableList()); - for (String notificator : notificators) { - Context.getNotificatorManager().getNotificator(notificator).sendAsync(userId, event, position); - } + if (!notifications.isEmpty()) { + if (position != null && position.getAddress() == null && geocodeOnRequest && geocoder != null) { + position.setAddress(geocoder.getAddress(position.getLatitude(), position.getLongitude(), null)); } + + notifications.forEach(notification -> { + cacheManager.getNotificationUsers(notification.getId(), event.getDeviceId()).forEach(user -> { + for (String notificator : notification.getNotificatorsTypes()) { + try { + notificatorManager.getNotificator(notificator).send(notification, user, event, position); + } catch (MessageException exception) { + LOGGER.warn("Notification failed", exception); + } + } + }); + }); } - if (Context.getEventForwarder() != null) { - Context.getEventForwarder().forwardEvent(event, position, usersToForward); - } + + forwardEvent(event, position); } - public void updateEvents(Map<Event, Position> events) { - for (Entry<Event, Position> event : events.entrySet()) { - updateEvent(event.getKey(), event.getValue()); + private void forwardEvent(Event event, Position position) { + if (eventForwarder != null) { + EventData eventData = new EventData(); + eventData.setEvent(event); + eventData.setPosition(position); + eventData.setDevice(cacheManager.getObject(Device.class, event.getDeviceId())); + if (event.getGeofenceId() != 0) { + eventData.setGeofence(cacheManager.getObject(Geofence.class, event.getGeofenceId())); + } + if (event.getMaintenanceId() != 0) { + eventData.setMaintenance(cacheManager.getObject(Maintenance.class, event.getMaintenanceId())); + } + eventForwarder.forward(eventData, (success, throwable) -> { + if (!success) { + LOGGER.warn("Event forwarding failed", throwable); + } + }); } } - public Set<Typed> getAllNotificationTypes() { - Set<Typed> types = new HashSet<>(); - Field[] fields = Event.class.getDeclaredFields(); - for (Field field : fields) { - if (Modifier.isStatic(field.getModifiers()) && field.getName().startsWith("TYPE_")) { - try { - types.add(new Typed(field.get(null).toString())); - } catch (IllegalArgumentException | IllegalAccessException error) { - LOGGER.warn("Get event types error", error); - } + public void updateEvents(Map<Event, Position> events) { + for (Entry<Event, Position> entry : events.entrySet()) { + Event event = entry.getKey(); + Position position = entry.getValue(); + try { + cacheManager.addDevice(event.getDeviceId()); + updateEvent(event, position); + } catch (StorageException e) { + throw new RuntimeException(e); + } finally { + cacheManager.removeDevice(event.getDeviceId()); } } - return types; } } diff --git a/src/main/java/org/traccar/database/OpenIdProvider.java b/src/main/java/org/traccar/database/OpenIdProvider.java new file mode 100644 index 000000000..312be8890 --- /dev/null +++ b/src/main/java/org/traccar/database/OpenIdProvider.java @@ -0,0 +1,203 @@ +/* + * Copyright 2023 Daniel Raper (me@danr.uk) + * + * 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.database; + +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.api.resource.SessionResource; +import org.traccar.api.security.LoginService; +import org.traccar.model.User; +import org.traccar.storage.StorageException; +import org.traccar.helper.LogAction; +import org.traccar.helper.WebHelper; + +import java.net.URI; +import java.net.URISyntaxException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse.BodyHandlers; +import java.security.GeneralSecurityException; +import java.util.List; +import java.util.Map; +import java.io.IOException; +import jakarta.servlet.http.HttpServletRequest; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.inject.Inject; + +import com.nimbusds.oauth2.sdk.http.HTTPResponse; +import com.nimbusds.oauth2.sdk.AuthorizationCode; +import com.nimbusds.oauth2.sdk.ResponseType; +import com.nimbusds.oauth2.sdk.Scope; +import com.nimbusds.oauth2.sdk.AuthorizationGrant; +import com.nimbusds.oauth2.sdk.TokenRequest; +import com.nimbusds.oauth2.sdk.TokenResponse; +import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant; +import com.nimbusds.oauth2.sdk.ParseException; +import com.nimbusds.oauth2.sdk.AuthorizationResponse; +import com.nimbusds.oauth2.sdk.auth.Secret; +import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic; +import com.nimbusds.oauth2.sdk.auth.ClientAuthentication; +import com.nimbusds.oauth2.sdk.token.BearerAccessToken; +import com.nimbusds.oauth2.sdk.id.State; +import com.nimbusds.oauth2.sdk.id.ClientID; +import com.nimbusds.openid.connect.sdk.OIDCTokenResponse; +import com.nimbusds.openid.connect.sdk.OIDCTokenResponseParser; +import com.nimbusds.openid.connect.sdk.UserInfoResponse; +import com.nimbusds.openid.connect.sdk.UserInfoRequest; +import com.nimbusds.openid.connect.sdk.AuthenticationRequest; +import com.nimbusds.openid.connect.sdk.claims.UserInfo; + +public class OpenIdProvider { + private final Boolean force; + private final ClientID clientId; + private final ClientAuthentication clientAuth; + private URI callbackUrl; + private URI authUrl; + private URI tokenUrl; + private URI userInfoUrl; + private URI baseUrl; + private final String adminGroup; + private final String allowGroup; + + private LoginService loginService; + + @Inject + public OpenIdProvider( + Config config, LoginService loginService, HttpClient httpClient, ObjectMapper objectMapper + ) throws InterruptedException, IOException, URISyntaxException { + this.loginService = loginService; + + force = config.getBoolean(Keys.OPENID_FORCE); + clientId = new ClientID(config.getString(Keys.OPENID_CLIENT_ID)); + clientAuth = new ClientSecretBasic(clientId, new Secret(config.getString(Keys.OPENID_CLIENT_SECRET))); + + baseUrl = new URI(WebHelper.retrieveWebUrl(config)); + callbackUrl = new URI(WebHelper.retrieveWebUrl(config) + "/api/session/openid/callback"); + + if (config.hasKey(Keys.OPENID_ISSUER_URL)) { + HttpRequest httpRequest = HttpRequest.newBuilder( + URI.create(config.getString(Keys.OPENID_ISSUER_URL) + "/.well-known/openid-configuration")) + .header("Accept", "application/json") + .build(); + + String httpResponse = httpClient.send(httpRequest, BodyHandlers.ofString()).body(); + + Map<String, Object> discoveryMap = objectMapper.readValue( + httpResponse, new TypeReference<Map<String, Object>>() { }); + + authUrl = new URI((String) discoveryMap.get("authorization_endpoint")); + tokenUrl = new URI((String) discoveryMap.get("token_endpoint")); + userInfoUrl = new URI((String) discoveryMap.get("userinfo_endpoint")); + } else { + authUrl = new URI(config.getString(Keys.OPENID_AUTH_URL)); + tokenUrl = new URI(config.getString(Keys.OPENID_TOKEN_URL)); + userInfoUrl = new URI(config.getString(Keys.OPENID_USERINFO_URL)); + } + + adminGroup = config.getString(Keys.OPENID_ADMIN_GROUP); + allowGroup = config.getString(Keys.OPENID_ALLOW_GROUP); + } + + public URI createAuthUri() { + Scope scope = new Scope("openid", "profile", "email"); + + if (adminGroup != null) { + scope.add("groups"); + } + + AuthenticationRequest.Builder request = new AuthenticationRequest.Builder( + new ResponseType("code"), + scope, + clientId, + callbackUrl); + + return request.endpointURI(authUrl) + .state(new State()) + .build() + .toURI(); + } + + private OIDCTokenResponse getToken( + AuthorizationCode code) throws IOException, ParseException, GeneralSecurityException { + AuthorizationGrant codeGrant = new AuthorizationCodeGrant(code, callbackUrl); + TokenRequest tokenRequest = new TokenRequest(tokenUrl, clientAuth, codeGrant); + + HTTPResponse tokenResponse = tokenRequest.toHTTPRequest().send(); + TokenResponse token = OIDCTokenResponseParser.parse(tokenResponse); + if (!token.indicatesSuccess()) { + throw new GeneralSecurityException("Unable to authenticate with the OpenID Connect provider."); + } + + return (OIDCTokenResponse) token.toSuccessResponse(); + } + + private UserInfo getUserInfo(BearerAccessToken token) throws IOException, ParseException, GeneralSecurityException { + HTTPResponse httpResponse = new UserInfoRequest(userInfoUrl, token) + .toHTTPRequest() + .send(); + + UserInfoResponse userInfoResponse = UserInfoResponse.parse(httpResponse); + + if (!userInfoResponse.indicatesSuccess()) { + throw new GeneralSecurityException( + "Failed to access OpenID Connect user info endpoint. Please contact your administrator."); + } + + return userInfoResponse.toSuccessResponse().getUserInfo(); + } + + public URI handleCallback( + URI requestUri, HttpServletRequest request + ) throws StorageException, ParseException, IOException, GeneralSecurityException { + AuthorizationResponse response = AuthorizationResponse.parse(requestUri); + + if (!response.indicatesSuccess()) { + throw new GeneralSecurityException(response.toErrorResponse().getErrorObject().getDescription()); + } + + AuthorizationCode authCode = response.toSuccessResponse().getAuthorizationCode(); + + if (authCode == null) { + throw new GeneralSecurityException("Malformed OpenID callback."); + } + + OIDCTokenResponse tokens = getToken(authCode); + + BearerAccessToken bearerToken = tokens.getOIDCTokens().getBearerAccessToken(); + + UserInfo userInfo = getUserInfo(bearerToken); + + List<String> userGroups = userInfo.getStringListClaim("groups"); + Boolean administrator = adminGroup != null && userGroups.contains(adminGroup); + + if (!(administrator || allowGroup == null || userGroups.contains(allowGroup))) { + throw new GeneralSecurityException("Your OpenID Groups do not permit access to Traccar."); + } + + User user = loginService.login(userInfo.getEmailAddress(), userInfo.getName(), administrator); + + request.getSession().setAttribute(SessionResource.USER_ID_KEY, user.getId()); + LogAction.login(user.getId(), WebHelper.retrieveRemoteAddress(request)); + + return baseUrl; + } + + public boolean getForce() { + return force; + } +} diff --git a/src/main/java/org/traccar/database/OrderManager.java b/src/main/java/org/traccar/database/OrderManager.java deleted file mode 100644 index c3253e52f..000000000 --- a/src/main/java/org/traccar/database/OrderManager.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2021 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.database; - -import org.traccar.model.Order; - -public class OrderManager extends ExtendedObjectManager<Order> { - - public OrderManager(DataManager dataManager) { - super(dataManager, Order.class); - } - -} diff --git a/src/main/java/org/traccar/database/PermissionsManager.java b/src/main/java/org/traccar/database/PermissionsManager.java deleted file mode 100644 index 2bb808033..000000000 --- a/src/main/java/org/traccar/database/PermissionsManager.java +++ /dev/null @@ -1,528 +0,0 @@ -/* - * 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. - * 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.database; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.Context; -import org.traccar.model.Attribute; -import org.traccar.model.BaseModel; -import org.traccar.model.Calendar; -import org.traccar.model.Command; -import org.traccar.model.Device; -import org.traccar.model.Driver; -import org.traccar.model.Geofence; -import org.traccar.model.Group; -import org.traccar.model.Maintenance; -import org.traccar.model.ManagedUser; -import org.traccar.model.Notification; -import org.traccar.model.Order; -import org.traccar.model.Permission; -import org.traccar.model.Server; -import org.traccar.model.User; -import org.traccar.storage.StorageException; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -public class PermissionsManager { - - private static final Logger LOGGER = LoggerFactory.getLogger(PermissionsManager.class); - - private final DataManager dataManager; - private final UsersManager usersManager; - - private volatile Server server; - - private final ReadWriteLock lock = new ReentrantReadWriteLock(); - - private final Map<Long, Set<Long>> groupPermissions = new HashMap<>(); - private final Map<Long, Set<Long>> devicePermissions = new HashMap<>(); - private final Map<Long, Set<Long>> deviceUsers = new HashMap<>(); - private final Map<Long, Set<Long>> groupDevices = new HashMap<>(); - - public PermissionsManager(DataManager dataManager, UsersManager usersManager) { - this.dataManager = dataManager; - this.usersManager = usersManager; - refreshServer(); - refreshDeviceAndGroupPermissions(); - } - - protected final void readLock() { - lock.readLock().lock(); - } - - protected final void readUnlock() { - lock.readLock().unlock(); - } - - protected final void writeLock() { - lock.writeLock().lock(); - } - - protected final void writeUnlock() { - lock.writeLock().unlock(); - } - - public User getUser(long userId) { - readLock(); - try { - return usersManager.getById(userId); - } finally { - readUnlock(); - } - } - - public Set<Long> getGroupPermissions(long userId) { - readLock(); - try { - if (!groupPermissions.containsKey(userId)) { - groupPermissions.put(userId, new HashSet<>()); - } - return groupPermissions.get(userId); - } finally { - readUnlock(); - } - } - - public Set<Long> getDevicePermissions(long userId) { - readLock(); - try { - if (!devicePermissions.containsKey(userId)) { - devicePermissions.put(userId, new HashSet<>()); - } - return devicePermissions.get(userId); - } finally { - readUnlock(); - } - } - - private Set<Long> getAllDeviceUsers(long deviceId) { - readLock(); - try { - if (!deviceUsers.containsKey(deviceId)) { - deviceUsers.put(deviceId, new HashSet<>()); - } - return deviceUsers.get(deviceId); - } finally { - readUnlock(); - } - } - - public Set<Long> getDeviceUsers(long deviceId) { - Device device = Context.getIdentityManager().getById(deviceId); - if (device != null && !device.getDisabled()) { - return getAllDeviceUsers(deviceId); - } else { - Set<Long> result = new HashSet<>(); - for (long userId : getAllDeviceUsers(deviceId)) { - if (getUserAdmin(userId)) { - result.add(userId); - } - } - return result; - } - } - - public Set<Long> getGroupDevices(long groupId) { - readLock(); - try { - if (!groupDevices.containsKey(groupId)) { - groupDevices.put(groupId, new HashSet<>()); - } - return groupDevices.get(groupId); - } finally { - readUnlock(); - } - } - - public void refreshServer() { - try { - server = dataManager.getServer(); - } catch (StorageException error) { - LOGGER.warn("Refresh server config error", error); - } - } - - public final void refreshDeviceAndGroupPermissions() { - writeLock(); - try { - groupPermissions.clear(); - devicePermissions.clear(); - try { - GroupTree groupTree = new GroupTree(Context.getGroupsManager().getItems( - Context.getGroupsManager().getAllItems()), - Context.getDeviceManager().getAllDevices()); - for (Permission groupPermission : dataManager.getPermissions(User.class, Group.class)) { - Set<Long> userGroupPermissions = getGroupPermissions(groupPermission.getOwnerId()); - Set<Long> userDevicePermissions = getDevicePermissions(groupPermission.getOwnerId()); - userGroupPermissions.add(groupPermission.getPropertyId()); - for (Group group : groupTree.getGroups(groupPermission.getPropertyId())) { - userGroupPermissions.add(group.getId()); - } - for (Device device : groupTree.getDevices(groupPermission.getPropertyId())) { - userDevicePermissions.add(device.getId()); - } - } - - for (Permission devicePermission : dataManager.getPermissions(User.class, Device.class)) { - getDevicePermissions(devicePermission.getOwnerId()).add(devicePermission.getPropertyId()); - } - - groupDevices.clear(); - for (long groupId : Context.getGroupsManager().getAllItems()) { - for (Device device : groupTree.getDevices(groupId)) { - getGroupDevices(groupId).add(device.getId()); - } - } - - } catch (StorageException | ClassNotFoundException error) { - LOGGER.warn("Refresh device permissions error", error); - } - - deviceUsers.clear(); - for (Map.Entry<Long, Set<Long>> entry : devicePermissions.entrySet()) { - for (long deviceId : entry.getValue()) { - getAllDeviceUsers(deviceId).add(entry.getKey()); - } - } - } finally { - writeUnlock(); - } - } - - public boolean getUserAdmin(long userId) { - User user = getUser(userId); - return user != null && user.getAdministrator(); - } - - public void checkAdmin(long userId) throws SecurityException { - if (!getUserAdmin(userId)) { - throw new SecurityException("Admin access required"); - } - } - - public boolean getUserManager(long userId) { - User user = getUser(userId); - return user != null && user.getUserLimit() != 0; - } - - public void checkManager(long userId) throws SecurityException { - if (!getUserManager(userId)) { - throw new SecurityException("Manager access required"); - } - } - - public void checkManager(long userId, long managedUserId) throws SecurityException { - checkManager(userId); - if (!usersManager.getUserItems(userId).contains(managedUserId)) { - throw new SecurityException("User access denied"); - } - } - - public void checkUserLimit(long userId) throws SecurityException { - int userLimit = getUser(userId).getUserLimit(); - if (userLimit != -1 && usersManager.getUserItems(userId).size() >= userLimit) { - throw new SecurityException("Manager user limit reached"); - } - } - - public void checkDeviceLimit(long userId) throws SecurityException { - int deviceLimit = getUser(userId).getDeviceLimit(); - if (deviceLimit != -1) { - int deviceCount; - if (getUserManager(userId)) { - deviceCount = Context.getDeviceManager().getAllManagedItems(userId).size(); - } else { - deviceCount = Context.getDeviceManager().getAllUserItems(userId).size(); - } - if (deviceCount >= deviceLimit) { - throw new SecurityException("User device limit reached"); - } - } - } - - public boolean getUserReadonly(long userId) { - User user = getUser(userId); - return user != null && user.getReadonly(); - } - - public boolean getUserDeviceReadonly(long userId) { - User user = getUser(userId); - return user != null && user.getDeviceReadonly(); - } - - public boolean getUserLimitCommands(long userId) { - User user = getUser(userId); - return user != null && user.getLimitCommands(); - } - - public boolean getUserDisableReport(long userId) { - User user = getUser(userId); - return user != null && user.getDisableReports(); - } - - public void checkReadonly(long userId) throws SecurityException { - if (!getUserAdmin(userId) && (server.getReadonly() || getUserReadonly(userId))) { - throw new SecurityException("Account is readonly"); - } - } - - public void checkDeviceReadonly(long userId) throws SecurityException { - if (!getUserAdmin(userId) && (server.getDeviceReadonly() || getUserDeviceReadonly(userId))) { - throw new SecurityException("Account is device readonly"); - } - } - - public void checkLimitCommands(long userId) throws SecurityException { - if (!getUserAdmin(userId) && (server.getLimitCommands() || getUserLimitCommands(userId))) { - throw new SecurityException("Account has limit sending commands"); - } - } - - public void checkDisableReports(long userId) throws SecurityException { - if (!getUserAdmin(userId) && (server.getDisableReports() || getUserDisableReport(userId))) { - throw new SecurityException("Account has reports disabled"); - } - } - - public void checkUserDeviceCommand(long userId, long deviceId, long commandId) throws SecurityException { - if (!getUserAdmin(userId) && Context.getCommandsManager().checkDeviceCommand(deviceId, commandId)) { - throw new SecurityException("Command can not be sent to this device"); - } - } - - public void checkUserEnabled(long userId) throws SecurityException { - User user = getUser(userId); - if (user == null) { - throw new SecurityException("Unknown account"); - } - if (user.getDisabled()) { - throw new SecurityException("Account is disabled"); - } - if (user.getExpirationTime() != null && System.currentTimeMillis() > user.getExpirationTime().getTime()) { - throw new SecurityException("Account has expired"); - } - } - - public void checkUserUpdate(long userId, User before, User after) throws SecurityException { - if (before.getAdministrator() != after.getAdministrator() - || before.getDeviceLimit() != after.getDeviceLimit() - || before.getUserLimit() != after.getUserLimit()) { - checkAdmin(userId); - } - User user = getUser(userId); - if (user != null && user.getExpirationTime() != null - && (after.getExpirationTime() == null - || user.getExpirationTime().compareTo(after.getExpirationTime()) < 0)) { - checkAdmin(userId); - } - if (before.getReadonly() != after.getReadonly() - || before.getDeviceReadonly() != after.getDeviceReadonly() - || before.getDisabled() != after.getDisabled() - || before.getLimitCommands() != after.getLimitCommands() - || before.getDisableReports() != after.getDisableReports()) { - if (userId == after.getId()) { - checkAdmin(userId); - } - if (!getUserAdmin(userId)) { - checkManager(userId); - } - } - } - - public void checkUser(long userId, long managedUserId) throws SecurityException { - if (userId != managedUserId && !getUserAdmin(userId)) { - checkManager(userId, managedUserId); - } - } - - public void checkGroup(long userId, long groupId) throws SecurityException { - if (!getGroupPermissions(userId).contains(groupId) && !getUserAdmin(userId)) { - checkManager(userId); - for (long managedUserId : usersManager.getUserItems(userId)) { - if (getGroupPermissions(managedUserId).contains(groupId)) { - return; - } - } - throw new SecurityException("Group access denied"); - } - } - - public void checkDevice(long userId, long deviceId) throws SecurityException { - if (!Context.getDeviceManager().getUserItems(userId).contains(deviceId) && !getUserAdmin(userId)) { - checkManager(userId); - for (long managedUserId : usersManager.getUserItems(userId)) { - if (Context.getDeviceManager().getUserItems(managedUserId).contains(deviceId)) { - return; - } - } - throw new SecurityException("Device access denied"); - } - } - - public void checkRegistration(long userId) { - if (!server.getRegistration() && !getUserAdmin(userId)) { - throw new SecurityException("Registration disabled"); - } - } - - public void checkPermission(Class<?> object, long userId, long objectId) - throws SecurityException { - SimpleObjectManager<? extends BaseModel> manager = null; - - if (object.equals(Device.class)) { - checkDevice(userId, objectId); - } else if (object.equals(Group.class)) { - checkGroup(userId, objectId); - } else if (object.equals(User.class) || object.equals(ManagedUser.class)) { - checkUser(userId, objectId); - } else if (object.equals(Geofence.class)) { - manager = Context.getGeofenceManager(); - } else if (object.equals(Attribute.class)) { - manager = Context.getAttributesManager(); - } else if (object.equals(Driver.class)) { - manager = Context.getDriversManager(); - } else if (object.equals(Calendar.class)) { - manager = Context.getCalendarManager(); - } else if (object.equals(Command.class)) { - manager = Context.getCommandsManager(); - } else if (object.equals(Maintenance.class)) { - manager = Context.getMaintenancesManager(); - } else if (object.equals(Notification.class)) { - manager = Context.getNotificationManager(); - } else if (object.equals(Order.class)) { - manager = Context.getOrderManager(); - } else { - throw new IllegalArgumentException("Unknown object type"); - } - - if (manager != null && !manager.checkItemPermission(userId, objectId) && !getUserAdmin(userId)) { - checkManager(userId); - for (long managedUserId : usersManager.getManagedItems(userId)) { - if (manager.checkItemPermission(managedUserId, objectId)) { - return; - } - } - throw new SecurityException("Type " + object + " access denied"); - } - } - - public void refreshAllUsersPermissions() { - if (Context.getGeofenceManager() != null) { - Context.getGeofenceManager().refreshUserItems(); - } - Context.getCalendarManager().refreshUserItems(); - Context.getDriversManager().refreshUserItems(); - Context.getAttributesManager().refreshUserItems(); - Context.getCommandsManager().refreshUserItems(); - Context.getMaintenancesManager().refreshUserItems(); - if (Context.getNotificationManager() != null) { - Context.getNotificationManager().refreshUserItems(); - } - } - - public void refreshAllExtendedPermissions() { - if (Context.getGeofenceManager() != null) { - Context.getGeofenceManager().refreshExtendedPermissions(); - } - Context.getDriversManager().refreshExtendedPermissions(); - Context.getAttributesManager().refreshExtendedPermissions(); - Context.getCommandsManager().refreshExtendedPermissions(); - Context.getMaintenancesManager().refreshExtendedPermissions(); - } - - public void refreshPermissions(Permission permission) { - if (permission.getOwnerClass().equals(User.class)) { - if (permission.getPropertyClass().equals(Device.class) - || permission.getPropertyClass().equals(Group.class)) { - refreshDeviceAndGroupPermissions(); - refreshAllExtendedPermissions(); - } else if (permission.getPropertyClass().equals(ManagedUser.class)) { - usersManager.refreshUserItems(); - } else if (permission.getPropertyClass().equals(Geofence.class) && Context.getGeofenceManager() != null) { - Context.getGeofenceManager().refreshUserItems(); - } else if (permission.getPropertyClass().equals(Driver.class)) { - Context.getDriversManager().refreshUserItems(); - } else if (permission.getPropertyClass().equals(Attribute.class)) { - Context.getAttributesManager().refreshUserItems(); - } else if (permission.getPropertyClass().equals(Calendar.class)) { - Context.getCalendarManager().refreshUserItems(); - } else if (permission.getPropertyClass().equals(Command.class)) { - Context.getCommandsManager().refreshUserItems(); - } else if (permission.getPropertyClass().equals(Maintenance.class)) { - Context.getMaintenancesManager().refreshUserItems(); - } else if (permission.getPropertyClass().equals(Order.class)) { - Context.getOrderManager().refreshUserItems(); - } else if (permission.getPropertyClass().equals(Notification.class) - && Context.getNotificationManager() != null) { - Context.getNotificationManager().refreshUserItems(); - } - } else if (permission.getOwnerClass().equals(Device.class) || permission.getOwnerClass().equals(Group.class)) { - if (permission.getPropertyClass().equals(Geofence.class) && Context.getGeofenceManager() != null) { - Context.getGeofenceManager().refreshExtendedPermissions(); - } else if (permission.getPropertyClass().equals(Driver.class)) { - Context.getDriversManager().refreshExtendedPermissions(); - } else if (permission.getPropertyClass().equals(Attribute.class)) { - Context.getAttributesManager().refreshExtendedPermissions(); - } else if (permission.getPropertyClass().equals(Command.class)) { - Context.getCommandsManager().refreshExtendedPermissions(); - } else if (permission.getPropertyClass().equals(Maintenance.class)) { - Context.getMaintenancesManager().refreshExtendedPermissions(); - } else if (permission.getPropertyClass().equals(Order.class)) { - Context.getOrderManager().refreshExtendedPermissions(); - } else if (permission.getPropertyClass().equals(Notification.class) - && Context.getNotificationManager() != null) { - Context.getNotificationManager().refreshExtendedPermissions(); - } - } - } - - public Server getServer() { - return server; - } - - public void updateServer(Server server) throws StorageException { - dataManager.updateObject(server); - this.server = server; - } - - public User login(String email, String password) throws StorageException { - User user = dataManager.login(email, password); - if (user != null) { - checkUserEnabled(user.getId()); - return getUser(user.getId()); - } - return null; - } - - public Object lookupAttribute(long userId, String key, Object defaultValue) { - Object preference; - Object serverPreference = server.getAttributes().get(key); - Object userPreference = getUser(userId).getAttributes().get(key); - if (server.getForceSettings()) { - preference = serverPreference != null ? serverPreference : userPreference; - } else { - preference = userPreference != null ? userPreference : serverPreference; - } - return preference != null ? preference : defaultValue; - } - -} diff --git a/src/main/java/org/traccar/database/SimpleObjectManager.java b/src/main/java/org/traccar/database/SimpleObjectManager.java deleted file mode 100644 index 78701720f..000000000 --- a/src/main/java/org/traccar/database/SimpleObjectManager.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) - * Copyright 2017 Andrey Kunitsyn (andrey@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.database; - -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.Context; -import org.traccar.model.BaseModel; -import org.traccar.model.Permission; -import org.traccar.model.User; -import org.traccar.storage.StorageException; - -public abstract class SimpleObjectManager<T extends BaseModel> extends BaseObjectManager<T> - implements ManagableObjects { - - private static final Logger LOGGER = LoggerFactory.getLogger(SimpleObjectManager.class); - - private Map<Long, Set<Long>> userItems; - - protected SimpleObjectManager(DataManager dataManager, Class<T> baseClass) { - super(dataManager, baseClass); - } - - @Override - public final Set<Long> getUserItems(long userId) { - try { - readLock(); - Set<Long> result = userItems.get(userId); - if (result != null) { - return new HashSet<>(result); - } else { - return new HashSet<>(); - } - } finally { - readUnlock(); - } - } - - @Override - public Set<Long> getManagedItems(long userId) { - Set<Long> result = getUserItems(userId); - for (long managedUserId : Context.getUsersManager().getUserItems(userId)) { - result.addAll(getUserItems(managedUserId)); - } - return result; - } - - public final boolean checkItemPermission(long userId, long itemId) { - return getUserItems(userId).contains(itemId); - } - - @Override - public void refreshItems() { - super.refreshItems(); - refreshUserItems(); - } - - public final void refreshUserItems() { - if (getDataManager() != null) { - try { - writeLock(); - userItems = new ConcurrentHashMap<>(); - for (Permission permission : getDataManager().getPermissions(User.class, getBaseClass())) { - Set<Long> items = userItems.computeIfAbsent(permission.getOwnerId(), key -> new HashSet<>()); - items.add(permission.getPropertyId()); - } - } catch (StorageException | ClassNotFoundException error) { - LOGGER.warn("Error getting permissions", error); - } finally { - writeUnlock(); - } - } - } - - @Override - public void removeItem(long itemId) throws StorageException { - super.removeItem(itemId); - refreshUserItems(); - } - -} diff --git a/src/main/java/org/traccar/database/StatisticsManager.java b/src/main/java/org/traccar/database/StatisticsManager.java index 3579ce7a5..445e53e7c 100644 --- a/src/main/java/org/traccar/database/StatisticsManager.java +++ b/src/main/java/org/traccar/database/StatisticsManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 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. @@ -19,16 +19,21 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.traccar.api.security.ServiceAccountUser; import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.helper.DateUtil; import org.traccar.model.Statistics; +import org.traccar.storage.Storage; import org.traccar.storage.StorageException; - -import javax.inject.Inject; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.Entity; -import javax.ws.rs.core.Form; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Request; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Form; import java.util.Calendar; import java.util.Date; import java.util.HashMap; @@ -37,6 +42,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +@Singleton public class StatisticsManager { private static final Logger LOGGER = LoggerFactory.getLogger(StatisticsManager.class); @@ -44,7 +50,7 @@ public class StatisticsManager { private static final int SPLIT_MODE = Calendar.DAY_OF_MONTH; private final Config config; - private final DataManager dataManager; + private final Storage storage; private final Client client; private final ObjectMapper objectMapper; @@ -52,6 +58,7 @@ public class StatisticsManager { private final Set<Long> users = new HashSet<>(); private final Map<Long, String> deviceProtocols = new HashMap<>(); + private final Map<Long, Integer> deviceMessages = new HashMap<>(); private int requests; private int messagesReceived; @@ -62,9 +69,9 @@ public class StatisticsManager { private int geolocationRequests; @Inject - public StatisticsManager(Config config, DataManager dataManager, Client client, ObjectMapper objectMapper) { + public StatisticsManager(Config config, Storage storage, Client client, ObjectMapper objectMapper) { this.config = config; - this.dataManager = dataManager; + this.storage = storage; this.client = client; this.objectMapper = objectMapper; } @@ -93,8 +100,11 @@ public class StatisticsManager { statistics.setProtocols(protocols); } + statistics.set("modern", config.getString(Keys.WEB_PATH).contains("modern")); + users.clear(); deviceProtocols.clear(); + deviceMessages.clear(); requests = 0; messagesReceived = 0; messagesStored = 0; @@ -105,7 +115,7 @@ public class StatisticsManager { } try { - dataManager.addObject(statistics); + storage.addObject(statistics, new Request(new Columns.Exclude("id"))); } catch (StorageException e) { LOGGER.warn("Error saving statistics", e); } @@ -133,6 +143,13 @@ public class StatisticsManager { LOGGER.warn("Failed to serialize protocols", e); } } + if (!statistics.getAttributes().isEmpty()) { + try { + form.param("attributes", objectMapper.writeValueAsString(statistics.getAttributes())); + } catch (JsonProcessingException e) { + LOGGER.warn("Failed to serialize attributes", e); + } + } client.target(url).request().async().post(Entity.form(form)); } @@ -142,7 +159,7 @@ public class StatisticsManager { public synchronized void registerRequest(long userId) { checkSplit(); requests += 1; - if (userId != 0) { + if (userId != 0 && userId != ServiceAccountUser.ID) { users.add(userId); } } @@ -157,9 +174,14 @@ public class StatisticsManager { messagesStored += 1; if (deviceId != 0) { deviceProtocols.put(deviceId, protocol); + deviceMessages.merge(deviceId, 1, Integer::sum); } } + public synchronized int messageStoredCount(long deviceId) { + return deviceMessages.getOrDefault(deviceId, 0); + } + public synchronized void registerMail() { checkSplit(); mailSent += 1; diff --git a/src/main/java/org/traccar/database/UsersManager.java b/src/main/java/org/traccar/database/UsersManager.java deleted file mode 100644 index 31759dc8b..000000000 --- a/src/main/java/org/traccar/database/UsersManager.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) - * Copyright 2017 Andrey Kunitsyn (andrey@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.database; - -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import org.traccar.model.User; -import org.traccar.storage.StorageException; - -public class UsersManager extends SimpleObjectManager<User> { - - private Map<String, User> usersTokens; - - public UsersManager(DataManager dataManager) { - super(dataManager, User.class); - if (usersTokens == null) { - usersTokens = new ConcurrentHashMap<>(); - } - } - - private void putToken(User user) { - if (usersTokens == null) { - usersTokens = new ConcurrentHashMap<>(); - } - if (user.getToken() != null) { - usersTokens.put(user.getToken(), user); - } - } - - @Override - protected void addNewItem(User user) { - super.addNewItem(user); - putToken(user); - } - - @Override - protected void updateCachedItem(User user) { - User cachedUser = getById(user.getId()); - super.updateCachedItem(user); - putToken(user); - if (cachedUser.getToken() != null && !cachedUser.getToken().equals(user.getToken())) { - usersTokens.remove(cachedUser.getToken()); - } - } - - @Override - public void updateItem(User user) throws StorageException { - if (user.getHashedPassword() != null) { - getDataManager().updateUserPassword(user); - } - super.updateItem(user); - } - - @Override - protected void removeCachedItem(long userId) { - User cachedUser = getById(userId); - if (cachedUser != null) { - String userToken = cachedUser.getToken(); - super.removeCachedItem(userId); - if (userToken != null) { - usersTokens.remove(userToken); - } - } - } - - @Override - public Set<Long> getManagedItems(long userId) { - Set<Long> result = getUserItems(userId); - result.add(userId); - return result; - } - - public User getUserByToken(String token) { - return usersTokens.get(token); - } - -} |