/*
* 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.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.traccar.Context;
import org.traccar.helper.Log;
import org.traccar.model.Device;
import org.traccar.model.DeviceGeofence;
import org.traccar.model.Geofence;
import org.traccar.model.GeofencePermission;
import org.traccar.model.GroupGeofence;
import org.traccar.model.Position;
public class GeofenceManager {
private final DataManager dataManager;
private final Map<Long, Geofence> geofences = new HashMap<>();
private final Map<Long, Set<Long>> userGeofences = new HashMap<>();
private final Map<Long, Set<Long>> groupGeofences = new HashMap<>();
private final Map<Long, Set<Long>> deviceGeofencesWithGroups = new HashMap<>();
private final Map<Long, Set<Long>> deviceGeofences = new HashMap<>();
private final ReadWriteLock deviceGeofencesLock = new ReentrantReadWriteLock();
private final ReadWriteLock geofencesLock = new ReentrantReadWriteLock();
private final ReadWriteLock groupGeofencesLock = new ReentrantReadWriteLock();
private final ReadWriteLock userGeofencesLock = new ReentrantReadWriteLock();
public GeofenceManager(DataManager dataManager) {
this.dataManager = dataManager;
refreshGeofences();
}
private Set<Long> getUserGeofences(long userId) {
if (!userGeofences.containsKey(userId)) {
userGeofences.put(userId, new HashSet<Long>());
}
return userGeofences.get(userId);
}
public Set<Long> getUserGeofencesIds(long userId) {
userGeofencesLock.readLock().lock();
try {
return getUserGeofences(userId);
} finally {
userGeofencesLock.readLock().unlock();
}
}
private Set<Long> getGroupGeofences(long groupId) {
if (!groupGeofences.containsKey(groupId)) {
groupGeofences.put(groupId, new HashSet<Long>());
}
return groupGeofences.get(groupId);
}
public Set<Long> getGroupGeofencesIds(long groupId) {
groupGeofencesLock.readLock().lock();
try {
return getGroupGeofences(groupId);
} finally {
groupGeofencesLock.readLock().unlock();
}
}
public Set<Long> getAllDeviceGeofences(long deviceId) {
deviceGeofencesLock.readLock().lock();
try {
return getDeviceGeofences(deviceGeofencesWithGroups, deviceId);
} finally {
deviceGeofencesLock.readLock().unlock();
}
}
public Set<Long> getDeviceGeofencesIds(long deviceId) {
deviceGeofencesLock.readLock().lock();
try {
return getDeviceGeofences(deviceGeofences, deviceId);
} finally {
deviceGeofencesLock.readLock().unlock();
}
}
private Set<Long> getDeviceGeofences(Map<Long, Set<Long>> deviceGeofences, long deviceId) {
if (!deviceGeofences.containsKey(deviceId)) {
deviceGeofences.put(deviceId, new HashSet<Long>());
}
return deviceGeofences.get(deviceId);
}
public final void refreshGeofences() {
if (dataManager != null) {
try {
geofencesLock.writeLock().lock();
try {
geofences.clear();
for (Geofence geofence : dataManager.getGeofences()) {
geofences.put(geofence.getId(), geofence);
}
} finally {
geofencesLock.writeLock().unlock();
}
} catch (SQLException error) {
Log.warning(error);
}
}
refreshUserGeofences();
refresh();
}
public final void refreshUserGeofences() {
if (dataManager != null) {
try {
userGeofencesLock.writeLock().lock();
try {
userGeofences.clear();
for (GeofencePermission geofencePermission : dataManager.getGeofencePermissions()) {
getUserGeofences(geofencePermission.getUserId()).add(geofencePermission.getGeofenceId());
}
} finally {
userGeofencesLock.writeLock().unlock();
}
} catch (SQLException error) {
Log.warning(error);
}
}
}
public final void refresh() {
if (dataManager != null) {
try {
Collection<GroupGeofence> databaseGroupGeofences = dataManager.getGroupGeofences();
groupGeofencesLock.writeLock().lock();
try {
groupGeofences.clear();
for (GroupGeofence groupGeofence : databaseGroupGeofences) {
getGroupGeofences(groupGeofence.getGroupId()).add(groupGeofence.getGeofenceId());
}
} finally {
groupGeofencesLock.writeLock().unlock();
}
Collection<DeviceGeofence> databaseDeviceGeofences = dataManager.getDeviceGeofences();
Collection<Device> allDevices = Context.getDeviceManager().getAllDevices();
groupGeofencesLock.readLock().lock();
deviceGeofencesLock.writeLock().lock();
try {
deviceGeofences.clear();
deviceGeofencesWithGroups.clear();
for (DeviceGeofence deviceGeofence : databaseDeviceGeofences) {
getDeviceGeofences(deviceGeofences, deviceGeofence.getDeviceId())
.add(deviceGeofence.getGeofenceId());
getDeviceGeofences(deviceGeofencesWithGroups, deviceGeofence.getDeviceId())
.add(deviceGeofence.getGeofenceId());
}
for (Device device : allDevices) {
long groupId = device.getGroupId();
while (groupId != 0) {
getDeviceGeofences(deviceGeofencesWithGroups,
device.getId()).addAll(getGroupGeofences(groupId));
if (Context.getDeviceManager().getGroupById(groupId) != null) {
groupId = Context.getDeviceManager().getGroupById(groupId).getGroupId();
} else {
groupId = 0;
}
}
List<Long> deviceGeofenceIds = device.getGeofenceIds();
if (deviceGeofenceIds == null) {
deviceGeofenceIds = new ArrayList<>();
} else {
deviceGeofenceIds.clear();
}
Position lastPosition = Context.getIdentityManager().getLastPosition(device.getId());
if (lastPosition != null && deviceGeofencesWithGroups.containsKey(device.getId())) {
for (long geofenceId : deviceGeofencesWithGroups.get(device.getId())) {
Geofence geofence = getGeofence(geofenceId);
if (geofence != null && geofence.getGeometry()
.containsPoint(lastPosition.getLatitude(), lastPosition.getLongitude())) {
deviceGeofenceIds.add(geofenceId);
}
}
}
device.setGeofenceIds(deviceGeofenceIds);
}
} finally {
deviceGeofencesLock.writeLock().unlock();
groupGeofencesLock.readLock().unlock();
}
} catch (SQLException error) {
Log.warning(error);
}
}
}
public final Collection<Geofence> getAllGeofences() {
geofencesLock.readLock().lock();
try {
return geofences.values();
} finally {
geofencesLock.readLock().unlock();
}
}
public final Set<Long> getAllGeofencesIds() {
geofencesLock.readLock().lock();
try {
return geofences.keySet();
} finally {
geofencesLock.readLock().unlock();
}
}
public final Set<Long> getManagedGeofencesIds(long userId) {
Set<Long> geofences = new HashSet<>();
geofences.addAll(getUserGeofencesIds(userId));
for (long managedUserId : Context.getPermissionsManager().getUserPermissions(userId)) {
geofences.addAll(getUserGeofencesIds(managedUserId));
}
return geofences;
}
public final Collection<Geofence> getGeofences(Set<Long> geofencesIds) {
geofencesLock.readLock().lock();
try {
Collection<Geofence> result = new LinkedList<>();
for (long geofenceId : geofencesIds) {
result.add(getGeofence(geofenceId));
}
return result;
} finally {
geofencesLock.readLock().unlock();
}
}
public final Geofence getGeofence(long geofenceId) {
geofencesLock.readLock().lock();
try {
return geofences.get(geofenceId);
} finally {
geofencesLock.readLock().unlock();
}
}
public final void updateGeofence(Geofence geofence) {
geofencesLock.writeLock().lock();
try {
geofences.put(geofence.getId(), geofence);
} finally {
geofencesLock.writeLock().unlock();
}
try {
dataManager.updateGeofence(geofence);
} catch (SQLException error) {
Log.warning(error);
}
}
public boolean checkGeofence(long userId, long geofenceId) {
return getUserGeofencesIds(userId).contains(geofenceId);
}
public List<Long> getCurrentDeviceGeofences(Position position) {
List<Long> result = new ArrayList<>();
for (long geofenceId : getAllDeviceGeofences(position.getDeviceId())) {
if (getGeofence(geofenceId).getGeometry().containsPoint(position.getLatitude(), position.getLongitude())) {
result.add(geofenceId);
}
}
return result;
}
}