/* * Copyright 2015 - 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 org.jboss.netty.channel.Channel; import org.jboss.netty.util.Timeout; import org.jboss.netty.util.TimerTask; import org.traccar.Context; import org.traccar.GlobalTimer; import org.traccar.Protocol; import org.traccar.events.OverspeedEventHandler; import org.traccar.helper.Log; import org.traccar.model.Device; import org.traccar.model.DeviceState; import org.traccar.model.Event; import org.traccar.model.Position; import org.traccar.reports.ReportUtils; import org.traccar.reports.model.TripsConfig; import java.net.SocketAddress; import java.sql.SQLException; import java.util.Date; 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 long DEFAULT_TIMEOUT = 600; private final long deviceTimeout; private final boolean enableStatusEvents; private final boolean updateDeviceState; private TripsConfig tripsConfig = null; private long minimalOverspeedDuration; private boolean overspeedNotRepeat; private final Map activeDevices = new ConcurrentHashMap<>(); private final Map> listeners = new ConcurrentHashMap<>(); private final Map timeouts = new ConcurrentHashMap<>(); public ConnectionManager() { deviceTimeout = Context.getConfig().getLong("status.timeout", DEFAULT_TIMEOUT) * 1000; enableStatusEvents = Context.getConfig().getBoolean("event.enable"); updateDeviceState = Context.getConfig().getBoolean("status.updateDeviceState"); if (updateDeviceState) { tripsConfig = ReportUtils.initTripsConfig(); minimalOverspeedDuration = Context.getConfig().getLong("event.overspeed.minimalDuration") * 1000; overspeedNotRepeat = Context.getConfig().getBoolean("event.overspeed.notRepeat"); } } 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 (enableStatusEvents && !status.equals(oldStatus)) { String eventType; Set events = new HashSet<>(); switch (status) { case Device.STATUS_ONLINE: eventType = Event.TYPE_DEVICE_ONLINE; break; case Device.STATUS_UNKNOWN: eventType = Event.TYPE_DEVICE_UNKNOWN; if (updateDeviceState) { events.addAll(updateDeviceState(deviceId)); } break; default: eventType = Event.TYPE_DEVICE_OFFLINE; if (updateDeviceState) { events.addAll(updateDeviceState(deviceId)); } break; } events.add(new Event(eventType, deviceId)); Context.getNotificationManager().updateEvents(events, null); } 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(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { if (!timeout.isCancelled()) { updateDevice(deviceId, Device.STATUS_UNKNOWN, null); } } }, deviceTimeout, TimeUnit.MILLISECONDS)); } try { Context.getDeviceManager().updateDeviceStatus(device); } catch (SQLException error) { Log.warning(error); } updateDevice(device); } public Set updateDeviceState(long deviceId) { DeviceState deviceState = Context.getDeviceManager().getDeviceState(deviceId); Set result = new HashSet<>(); long currentTime = System.currentTimeMillis(); if (deviceState.getMotionState() != null && deviceState.getMotionPosition() != null) { boolean newMotion = !deviceState.getMotionState(); Position motionPosition = deviceState.getMotionPosition(); long motionTime = motionPosition.getFixTime().getTime() + (newMotion ? tripsConfig.getMinimalTripDuration() : tripsConfig.getMinimalParkingDuration()); if (motionTime <= currentTime) { String eventType = newMotion ? Event.TYPE_DEVICE_MOVING : Event.TYPE_DEVICE_STOPPED; result.add(new Event(eventType, motionPosition.getDeviceId(), motionPosition.getId())); deviceState.setMotionState(newMotion); deviceState.setMotionPosition(null); } } if (deviceState.getOverspeedState() != null && !deviceState.getOverspeedState() && deviceState.getOverspeedPosition() != null) { double speedLimit = Context.getDeviceManager().lookupAttributeDouble(deviceId, OverspeedEventHandler.ATTRIBUTE_SPEED_LIMIT, 0, false); if (speedLimit != 0) { Position overspeedPosition = deviceState.getOverspeedPosition(); long overspeedTime = overspeedPosition.getFixTime().getTime(); if (overspeedTime + minimalOverspeedDuration <= currentTime) { Event event = new Event(Event.TYPE_DEVICE_OVERSPEED, overspeedPosition.getDeviceId(), overspeedPosition.getId()); event.set("speed", overspeedPosition.getSpeed()); event.set(OverspeedEventHandler.ATTRIBUTE_SPEED_LIMIT, speedLimit); result.add(event); deviceState.setOverspeedState(overspeedNotRepeat); deviceState.setOverspeedPosition(null); } } } return result; } 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 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); } }