/* Copyright (c) 2011 Danish Maritime Authority
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
package dk.dma.ais.abnormal.analyzer;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.name.Named;
import dk.dma.ais.abnormal.analyzer.analysis.Analysis;
import dk.dma.ais.abnormal.analyzer.analysis.CloseEncounterAnalysis;
import dk.dma.ais.abnormal.analyzer.analysis.CourseOverGroundAnalysis;
import dk.dma.ais.abnormal.analyzer.analysis.DriftAnalysis;
import dk.dma.ais.abnormal.analyzer.analysis.FreeFlowAnalysis;
import dk.dma.ais.abnormal.analyzer.analysis.ShipTypeAndSizeAnalysis;
import dk.dma.ais.abnormal.analyzer.analysis.SpeedOverGroundAnalysis;
import dk.dma.ais.abnormal.analyzer.analysis.SuddenSpeedChangeAnalysis;
import dk.dma.ais.filter.IPacketFilter;
import dk.dma.ais.message.AisMessage;
import dk.dma.ais.message.AisMessage5;
import dk.dma.ais.message.IPositionMessage;
import dk.dma.ais.packet.AisPacket;
import dk.dma.ais.tracker.eventEmittingTracker.EventEmittingTracker;
import org.apache.commons.configuration.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Set;
import java.util.function.Predicate;
/**
* Handler for read AIS packets
*/
public class PacketHandlerImpl implements PacketHandler {
static final Logger LOG = LoggerFactory.getLogger(PacketHandlerImpl.class);
{
LOG.debug(this.getClass().getSimpleName() + " created (" + this + ").");
}
private final Configuration configuration;
private final AppStatisticsService statisticsService;
private final EventEmittingTracker tracker;
private final Set<IPacketFilter> filters;
private final Predicate<AisPacket> shipNameFilter;
private final Injector injector;
private final Set<Analysis> analyses;
@Inject
public PacketHandlerImpl(
Configuration configuration,
Injector injector,
AppStatisticsService statisticsService,
EventEmittingTracker tracker,
Set<IPacketFilter> filters,
@Named("shipNameFilter") Predicate<AisPacket> shipNameFilter
) {
this.configuration = configuration;
this.injector = injector;
this.statisticsService = statisticsService;
this.tracker = tracker;
this.filters = filters;
this.shipNameFilter = shipNameFilter;
this.analyses = initAnalyses();
this.analyses.forEach(analysis -> analysis.start());
}
/**
* Receive and process one AisPacket.
*
* @param packet The AisPacket to process.
*/
public void accept(final AisPacket packet) {
statisticsService.incUnfilteredPacketCount();
if (filterPacket(packet)) {
statisticsService.incFilteredPacketCount();
AisMessage message = packet.tryGetAisMessage();
if (message == null) {
LOG.warn("Invalid packet: " + packet.getStringMessage());
return;
}
updateApplicationStatistics(message);
doWork(packet);
}
}
/**
* Returns true if packet passes all packet filters
* @param packet
* @return
*/
private boolean filterPacket(AisPacket packet) {
final boolean[] rejected = {false}; // TODO Use Groovy or Scala...
filters.forEach(f -> {
if (!rejected[0] && f.rejectedByFilter(packet)) {
rejected[0] = true;
if (LOG.isDebugEnabled()) {
LOG.debug("Packet dropped due to " + f.getClass().getSimpleName());
}
}
});
boolean shipNameFilterPassed = ! shipNameFilter.test(packet);
if (!shipNameFilterPassed && LOG.isDebugEnabled()) {
LOG.debug("Packet dropped due to shipNameFilter");
}
boolean filterPassed = !rejected[0] && shipNameFilterPassed;
if (LOG.isDebugEnabled()) {
LOG.debug("Packet " + (filterPassed ? "passed":"dropped") + ": " + packet.getStringMessage());
}
return filterPassed;
}
private void updateApplicationStatistics(AisMessage message) {
statisticsService.incMessageCount();
if (message instanceof IPositionMessage) {
statisticsService.incPosMsgCount();
} else if (message instanceof AisMessage5) {
statisticsService.incStatMsgCount();
}
}
private void doWork(AisPacket packet) {
tracker.update(packet);
}
Set<Analysis> initAnalyses() {
ImmutableSet.Builder<Analysis> builder = new ImmutableSet.Builder<>();
initAnalysis(builder,"cog", CourseOverGroundAnalysis.class);
initAnalysis(builder,"sog", SpeedOverGroundAnalysis.class);
initAnalysis(builder,"typesize", ShipTypeAndSizeAnalysis.class);
initAnalysis(builder,"suddenspeedchange", SuddenSpeedChangeAnalysis.class);
initAnalysis(builder,"drift", DriftAnalysis.class);
initAnalysis(builder,"closeencounter", CloseEncounterAnalysis.class);
initAnalysis(builder,"freeflow", FreeFlowAnalysis.class);
return builder.build();
}
Set<Analysis> getAnalyses() {
return this.analyses;
}
private void initAnalysis(ImmutableSet.Builder<Analysis> builder, String name, Class<? extends Analysis> analysisClass) {
if (configuration.getBoolean("analysis." + name + ".enabled")) {
builder.add(injector.getInstance(analysisClass));
LOG.info(analysisClass.getSimpleName() + " is enabled.");
} else {
LOG.info(analysisClass.getSimpleName() + " is disabled.");
}
}
}