diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoSensorFixStoreImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoSensorFixStoreImpl.java index 9459a46b8ed..4a834561e14 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoSensorFixStoreImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoSensorFixStoreImpl.java @@ -21,6 +21,7 @@ import com.mongodb.DBCursor; import com.mongodb.DBObject; import com.mongodb.QueryBuilder; import com.sap.sailing.domain.common.DeviceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.persistence.DomainObjectFactory; import com.sap.sailing.domain.persistence.MongoObjectFactory; @@ -184,7 +185,8 @@ public class MongoSensorFixStoreImpl implements MongoSensorFixStore { * Store fixes in batches, reducing metadata storage update. */ @Override - public void storeFixes(DeviceIdentifier device, Iterable fixes) { + public RegattaAndRaceIdentifier storeFixes(DeviceIdentifier device, Iterable fixes) { + RegattaAndRaceIdentifier maneuverChanged = null; if (!Util.isEmpty(fixes)) { try { final Object dbDeviceId = storeDeviceId(deviceServiceFinder, device); @@ -230,8 +232,9 @@ public class MongoSensorFixStoreImpl implements MongoSensorFixStore { logger.log(Level.WARNING, "Could not store fix in MongoDB"); e.printStackTrace(); } - notifyListeners(device, fixes); + maneuverChanged = notifyListeners(device, fixes); } + return maneuverChanged; } @Override @@ -239,7 +242,9 @@ public class MongoSensorFixStoreImpl implements MongoSensorFixStore { storeFixes(device, Collections.singletonList(fix)); } - private void notifyListeners(DeviceIdentifier device, Iterable fixes) { + private RegattaAndRaceIdentifier notifyListeners(DeviceIdentifier device, + Iterable fixes) { + RegattaAndRaceIdentifier raceWithChangedManeuver = null; @SuppressWarnings({ "unchecked", "rawtypes" }) final Set> listenersToInform = LockUtil.executeWithReadLockAndResult(listenersLock, () -> { return new HashSet<>(Util.>> get( @@ -247,9 +252,13 @@ public class MongoSensorFixStoreImpl implements MongoSensorFixStore { }); for (FixT fix : fixes) { for (FixReceivedListener listener : listenersToInform) { - listener.fixReceived(device, fix); + final RegattaAndRaceIdentifier didManeuverChangeOrNull = listener.fixReceived(device, fix); + if (didManeuverChangeOrNull != null) { + raceWithChangedManeuver = didManeuverChangeOrNull; + } } } + return raceWithChangedManeuver; } @Override diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/DeviceMappingsAndSensorFixStoreLockingTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/DeviceMappingsAndSensorFixStoreLockingTest.java index 383942f7784..5f1ca09fef7 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/DeviceMappingsAndSensorFixStoreLockingTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/DeviceMappingsAndSensorFixStoreLockingTest.java @@ -47,7 +47,9 @@ public class DeviceMappingsAndSensorFixStoreLockingTest extends AbstractGPSFixSt } catch (Exception e) { throw new RuntimeException(e); } - store.addListener((device, fix) -> {}, deviceIdentifier); + store.addListener((device, fix) -> { + return null; + }, deviceIdentifier); } @Override @@ -66,6 +68,7 @@ public class DeviceMappingsAndSensorFixStoreLockingTest extends AbstractGPSFixSt throw new RuntimeException(e); } mappings.forEachMappingOfDeviceIncludingTimePoint(device, new MillisecondsTimePoint(1), (mapping) ->{}); + return null; }, device); new Thread() { diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/GPSFixStoreListenerTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/GPSFixStoreListenerTest.java index 2174d0718e8..8cf289203d5 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/GPSFixStoreListenerTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/GPSFixStoreListenerTest.java @@ -10,6 +10,7 @@ import org.junit.Test; import org.junit.rules.Timeout; import com.sap.sailing.domain.common.DeviceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.persistence.racelog.tracking.impl.MongoSensorFixStoreImpl; import com.sap.sailing.domain.racelog.tracking.FixReceivedListener; @@ -42,7 +43,9 @@ public class GPSFixStoreListenerTest extends AbstractGPSFixStoreTest { barrier.await(100, TimeUnit.MILLISECONDS); // During iteration in the main thread this causes a modification that makes the iterator throw a // ConcurrentModificationException on next() - store.addListener((DeviceIdentifier device, GPSFixMoving fix) -> {}, device); + store.addListener((DeviceIdentifier device, GPSFixMoving fix) -> { + return null; + }, device); barrier.await(100, TimeUnit.MILLISECONDS); barrier.await(100, TimeUnit.MILLISECONDS); } catch (Exception e) { @@ -69,7 +72,7 @@ public class GPSFixStoreListenerTest extends AbstractGPSFixStoreTest { } @Override - public void fixReceived(DeviceIdentifier device, GPSFixMoving fix) { + public RegattaAndRaceIdentifier fixReceived(DeviceIdentifier device, GPSFixMoving fix) { try { barrier.await(100, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { @@ -77,6 +80,7 @@ public class GPSFixStoreListenerTest extends AbstractGPSFixStoreTest { } catch (Exception e) { throw new RuntimeException(e); } + return null; } } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/FixLoaderAndTracker.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/FixLoaderAndTracker.java index 32e38f5afd9..e63fa9d08ff 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/FixLoaderAndTracker.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/FixLoaderAndTracker.java @@ -5,9 +5,11 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeSet; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.logging.Level; @@ -27,6 +29,7 @@ import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.common.DeviceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.common.tracking.DoubleVectorFix; @@ -42,6 +45,7 @@ import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sailing.domain.tracking.DynamicSensorFixTrack; import com.sap.sailing.domain.tracking.DynamicTrack; import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.Maneuver; import com.sap.sailing.domain.tracking.RaceChangeListener; import com.sap.sailing.domain.tracking.Track; import com.sap.sailing.domain.tracking.TrackedRace; @@ -124,6 +128,8 @@ public class FixLoaderAndTracker implements TrackingDataLoader { * one more {@link TrackingDataLoader} that ensures the loading state of the associated {@link TrackedRace}. */ private final Set loadingJobs = ConcurrentHashMap.newKeySet(); + private final ConcurrentHashMap lastNotifiedManeuverCache = new ConcurrentHashMap<>(); + private final SensorFixMapperFactory sensorFixMapperFactory; /** @@ -170,12 +176,15 @@ public class FixLoaderAndTracker implements TrackingDataLoader { }; private final FixReceivedListener listener = new FixReceivedListener() { @Override - public void fixReceived(DeviceIdentifier device, Timed fix) { + public RegattaAndRaceIdentifier fixReceived(DeviceIdentifier device, Timed fix) { + AtomicReference maneuverChanged = new AtomicReference<>(); + if (!preemptiveStopRequested.get() && trackedRace.getStartOfTracking() != null) { final TimePoint timePoint = fix.getTimePoint(); - deviceMappings.forEachMappingOfDeviceIncludingTimePoint(device, fix.getTimePoint(), new Consumer>() { + deviceMappings.forEachMappingOfDeviceIncludingTimePoint(device, fix.getTimePoint(), + new Consumer>() { @Override - public void accept(DeviceMappingWithRegattaLogEvent mapping) { + public void accept(DeviceMappingWithRegattaLogEvent mapping) { mapping.getRegattaLogEvent().accept(new MappingEventVisitor() { @Override public void visit(RegattaLogDeviceCompetitorSensorDataMappingEvent event) { @@ -233,6 +242,11 @@ public class FixLoaderAndTracker implements TrackingDataLoader { } if (fix instanceof GPSFixMoving) { trackedRace.recordFix(comp, (GPSFixMoving) fix); + RegattaAndRaceIdentifier maneuverChangedAnswer = detectIfManeuverChanged( + comp); + if (maneuverChangedAnswer != null) { + maneuverChanged.set(maneuverChangedAnswer); + } } else { logger.log(Level.WARNING, String.format( @@ -308,9 +322,26 @@ public class FixLoaderAndTracker implements TrackingDataLoader { } }); } + return maneuverChanged.get(); } }; + private RegattaAndRaceIdentifier detectIfManeuverChanged(Competitor comp) { + boolean changed = false; + if (comp.getId() instanceof UUID) { + Maneuver lastDetectedManeuver = Util.last(trackedRace.getManeuvers(comp, false)); + if (lastDetectedManeuver != null) { + Maneuver lastNotifiedManeuverOrNull = lastNotifiedManeuverCache.get(comp.getId()); + if (!lastDetectedManeuver.equals(lastNotifiedManeuverOrNull)) { + lastNotifiedManeuverCache.put((UUID) comp.getId(), lastNotifiedManeuverOrNull); + changed = true; + logger.info(comp.getName() + " new maneuver is " + lastDetectedManeuver); + } + } + } + return changed ? trackedRace.getRaceIdentifier() : null; + } + public FixLoaderAndTracker(DynamicTrackedRace trackedRace, SensorFixStore sensorFixStore, SensorFixMapperFactory sensorFixMapperFactory) { this.sensorFixStore = sensorFixStore; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptySensorFixStore.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptySensorFixStore.java index 9601bc0022c..c61bbd6c481 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptySensorFixStore.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptySensorFixStore.java @@ -5,6 +5,7 @@ import java.util.function.BooleanSupplier; import java.util.function.Consumer; import com.sap.sailing.domain.common.DeviceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.common.TimePoint; @@ -54,7 +55,8 @@ public enum EmptySensorFixStore implements SensorFixStore { } @Override - public void storeFixes(DeviceIdentifier device, Iterable fixes) { + public RegattaAndRaceIdentifier storeFixes(DeviceIdentifier device, Iterable fixes) { + return null; } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/FixReceivedListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/FixReceivedListener.java index fdf017df4ef..c17cbfb32d4 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/FixReceivedListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/FixReceivedListener.java @@ -1,6 +1,7 @@ package com.sap.sailing.domain.racelog.tracking; import com.sap.sailing.domain.common.DeviceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sse.common.Timed; /** @@ -9,5 +10,5 @@ import com.sap.sse.common.Timed; * @param the type of fixes this listener can consume. */ public interface FixReceivedListener { - void fixReceived(DeviceIdentifier device, FixT fix); + RegattaAndRaceIdentifier fixReceived(DeviceIdentifier device, FixT fix); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/SensorFixStore.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/SensorFixStore.java index e3e348c7b1c..73519d34a3f 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/SensorFixStore.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/SensorFixStore.java @@ -5,6 +5,7 @@ import java.util.function.BooleanSupplier; import java.util.function.Consumer; import com.sap.sailing.domain.common.DeviceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.common.tracking.GPSFix; import com.sap.sse.common.NoCorrespondingServiceRegisteredException; @@ -73,7 +74,7 @@ public interface SensorFixStore { * @param fix * The fix to store. Must not be null. */ - void storeFixes(DeviceIdentifier device, Iterable fixes); + RegattaAndRaceIdentifier storeFixes(DeviceIdentifier device, Iterable fixes); /** * Listeners are notified, whenever a {@link GPSFix} submitted by the {@code device} diff --git a/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java b/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java index 709e5294686..b6e24433a93 100755 --- a/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java +++ b/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java @@ -41,6 +41,7 @@ import com.sap.sailing.declination.Declination; import com.sap.sailing.declination.DeclinationService; import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.Wind; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.impl.DegreePosition; @@ -152,10 +153,11 @@ public class UDPExpeditionReceiverTest { } @Override - public void storeFixes(DeviceIdentifier device, Iterable fixes) { + public RegattaAndRaceIdentifier storeFixes(DeviceIdentifier device, Iterable fixes) { for (final FixT fix : fixes) { storeFix(device, fix); } + return null; } @Override diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/GPSFixesResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/GPSFixesResource.java index 920fce58e72..2ee6175aaab 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/GPSFixesResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/GPSFixesResource.java @@ -17,6 +17,7 @@ import org.json.simple.JSONValue; import org.json.simple.parser.ParseException; import com.sap.sailing.domain.common.DeviceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.racelogtracking.impl.SmartphoneUUIDIdentifierImpl; import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; @@ -51,13 +52,17 @@ public class GPSFixesResource extends AbstractSailingServerResource { DeviceIdentifier device = new SmartphoneUUIDIdentifierImpl(data.getA()); List fixes = data.getB(); + JSONObject answer = new JSONObject(); try { - getService().getSensorFixStore().storeFixes(device, fixes); + RegattaAndRaceIdentifier raceWithManeuverChanged = getService().getSensorFixStore().storeFixes(device, + fixes); + if (raceWithManeuverChanged != null) { + answer.put("maneuverchanged", raceWithManeuverChanged); + } logger.log(Level.INFO, "Added " + fixes.size() + " fixes for device " + device.toString() + " to store"); } catch (NoCorrespondingServiceRegisteredException e) { logger.log(Level.WARNING, "Could not store fix for device " + device); } - - return Response.ok().build(); + return Response.ok(answer.toJSONString()).build(); } } \ No newline at end of file