bug 4087: using IControl ID + (1/2) for marks' ID; replace control points by existing ones for same set of marks

Change-Id: Icec17f89224aa0bef70d681362613bda849a42c2
This commit is contained in:
Axel Uhl committed 2017-04-06 12:14:29 +02:00
1 parent 90ac3aff2e
commit 8180a65da5
8 files changed
+138 -22

No files matched your search

@@ -52,7 +52,7 @@ public interface SharedDomainFactory extends CompetitorFactory {
Mark getOrCreateMark(String toStringRepresentationOfID, String name, MarkType type, String color, String shape, String pattern);
/**
* @param name also uses the name as the gate's ID; if you have a real ID, use {@link #createGate(Serializable, Mark, Mark, String)} instead
* @param name also uses the name as the gate's ID; if you have a real ID, use {@link #createControlPointWithTwoMarks(Serializable, Mark, Mark, String)} instead
*/
ControlPointWithTwoMarks createControlPointWithTwoMarks(Mark left, Mark right, String name);
@@ -53,6 +53,8 @@ import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.model.lib.api.event.ICompetitor;
import com.tractrac.model.lib.api.event.IEvent;
import com.tractrac.model.lib.api.event.IRace;
import com.tractrac.model.lib.api.route.IControl;
import com.tractrac.model.lib.api.route.IControlPoint;
import com.tractrac.subscription.lib.api.IEventSubscriber;
import com.tractrac.subscription.lib.api.IRaceSubscriber;
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
@@ -186,26 +188,26 @@ public interface DomainFactory {
* notification was sent, and that the {@link RaceDefinition} is already
* {@link com.sap.sailing.domain.base.Regatta#getAllRaces() known} by its
* containing {@link com.sap.sailing.domain.base.Regatta}.
*
* @param raceDefinitionSetToUpdate
* @param raceDefinitionSetToUpdate
* if not <code>null</code>, after creating the
* {@link TrackedRace}, the {@link RaceDefinition} is
* {@link DynamicRaceDefinitionSet#addRaceDefinition(RaceDefinition, DynamicTrackedRace)
* added} to that object.
* @param runBeforeExposingRace
* @param runBeforeExposingRace
* if not {@code null} then this consumer will be passed the
* {@link DynamicTrackedRace} if it was actually created by this
* call. This happens while still in the
* {@code synchronized(raceCache)} block, therefore before calls
* waiting for the race (e.g.,
* {@link #getAndWaitForRaceDefinition(UUID)}) return the race.
* @param tractracRace TODO
*/
DynamicTrackedRace getOrCreateRaceDefinitionAndTrackedRace(DynamicTrackedRegatta trackedRegatta, UUID raceId,
String raceName, Iterable<com.sap.sailing.domain.base.Competitor> competitors, BoatClass boatClass,
Map<Competitor, Boat> competitorBoats, Course course, Iterable<Sideline> sidelines, WindStore windStore,
long delayToLiveInMillis, long millisecondsOverWhichToAverageWind,
DynamicRaceDefinitionSet raceDefinitionSetToUpdate, URI courseDesignUpdateURI, UUID tracTracEventUuid,
String tracTracUsername, String tracTracPassword, boolean ignoreTracTracMarkPassings, RaceLogResolver raceLogResolver, Consumer<DynamicTrackedRace> runBeforeExposingRace);
String tracTracUsername, String tracTracPassword, boolean ignoreTracTracMarkPassings, RaceLogResolver raceLogResolver, Consumer<DynamicTrackedRace> runBeforeExposingRace, IRace tractracRace);
/**
* The record may be for a single mark or a gate. If for a gate, the
@@ -308,6 +310,28 @@ public interface DomainFactory {
* the start time and whether a race was aborted.
*/
void addTracTracUpdateHandlers(URI tracTracUpdateURI, UUID tracTracEventUuid, String tracTracUsername,
String tracTracPassword, RaceDefinition raceDefinition, DynamicTrackedRace trackedRace);
String tracTracPassword, RaceDefinition raceDefinition, DynamicTrackedRace trackedRace, IRace tractracRace);
/**
* Since TracAPI 3.6.1 the TracAPI provides a course area name for {@link IRace} objects. Furthermore, the
* {@link IControl} control points can now tell their {@link IControl#getCourseArea() course area}. This allows
* us to fetch the {@link IControl}s for a specific course area, thereby, e.g., restricting the marks of an
* event that we offer to the Race Manager app's course designer to those available on the course area.
*/
Iterable<IControl> getControlsForCourseArea(IEvent tracTracEvent, String tracTracCourseAreaName);
/**
* Since TracAPI 3.6.1 the TracAPI provides a course area name for {@link IRace} objects. Furthermore, the
* {@link IControl} control points can now tell their {@link IControl#getCourseArea() course area}. This allows
* us to fetch the {@link IControlPoint}s for a specific course area, thereby, e.g., restricting the marks of an
* event that we offer to the Race Manager app's course designer to those available on the course area.
*/
Iterable<IControlPoint> getControlPointsForCourseArea(IEvent tracTracEvent, String tracTracCourseAreaName);
/**
* Looks for an {@link IControl} in the {@code candidates} that contains two {@link IControlPoint}s
* that map to the {@code first} and {@code second} mark.
*/
IControl getExistingControlWithTwoMarks(Iterable<IControl> candidates, Mark first, Mark second);
}
@@ -11,10 +11,12 @@ import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -90,6 +92,8 @@ import com.tractrac.model.lib.api.event.ICompetitorClass;
import com.tractrac.model.lib.api.event.IEvent;
import com.tractrac.model.lib.api.event.IRace;
import com.tractrac.model.lib.api.event.IRaceCompetitor;
import com.tractrac.model.lib.api.route.IControl;
import com.tractrac.model.lib.api.route.IControlPoint;
import com.tractrac.subscription.lib.api.IEventSubscriber;
import com.tractrac.subscription.lib.api.IRaceSubscriber;
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
@@ -514,7 +518,7 @@ public class DomainFactoryImpl implements DomainFactory {
long delayToLiveInMillis, long millisecondsOverWhichToAverageWind,
DynamicRaceDefinitionSet raceDefinitionSetToUpdate, URI tracTracUpdateURI, UUID tracTracEventUuid,
String tracTracUsername, String tracTracPassword, boolean ignoreTracTracMarkPassings, RaceLogResolver raceLogResolver,
Consumer<DynamicTrackedRace> runBeforeExposingRace) {
Consumer<DynamicTrackedRace> runBeforeExposingRace, IRace tractracRace) {
synchronized (raceCache) {
RaceDefinition raceDefinition = raceCache.get(raceId);
if (raceDefinition == null) {
@@ -537,7 +541,7 @@ public class DomainFactoryImpl implements DomainFactory {
runBeforeExposingRace.accept(trackedRace);
}
addTracTracUpdateHandlers(tracTracUpdateURI, tracTracEventUuid, tracTracUsername, tracTracPassword,
raceDefinition, trackedRace);
raceDefinition, trackedRace, tractracRace);
raceCache.put(raceId, raceDefinition);
raceCache.notifyAll();
} else {
@@ -559,12 +563,48 @@ public class DomainFactoryImpl implements DomainFactory {
}
}
@Override
public Iterable<IControlPoint> getControlPointsForCourseArea(IEvent tracTracEvent, String tracTracCourseAreaName) {
final Set<IControlPoint> result = new HashSet<>();
for (final IControl control : getControlsForCourseArea(tracTracEvent, tracTracCourseAreaName)) {
result.addAll(control.getControlPoints());
}
return result;
}
@Override
public Iterable<IControl> getControlsForCourseArea(IEvent tracTracEvent, String tracTracCourseAreaName) {
final Set<IControl> result = new HashSet<>();
for (final IControl control : tracTracEvent.getControls()) {
if (Util.equalsWithNull(control.getCourseArea(), tracTracCourseAreaName)) {
result.add(control);
}
}
return result;
}
@Override
public IControl getExistingControlWithTwoMarks(Iterable<IControl> candidates, Mark first, Mark second) {
final Set<Mark> pairOfMarksToFind = new HashSet<>();
pairOfMarksToFind.add(first);
pairOfMarksToFind.add(second);
for (final IControl control : candidates) {
TracTracControlPoint cp = new ControlPointAdapter(control);
Set<Mark> marksInExistingControlPoint = new HashSet<>();
Util.addAll(getOrCreateControlPoint(cp).getMarks(), marksInExistingControlPoint);
if (marksInExistingControlPoint.equals(pairOfMarksToFind)) {
return control;
}
}
return null;
}
@Override
public void addTracTracUpdateHandlers(URI tracTracUpdateURI, UUID tracTracEventUuid, String tracTracUsername,
String tracTracPassword, RaceDefinition raceDefinition, DynamicTrackedRace trackedRace) {
String tracTracPassword, RaceDefinition raceDefinition, DynamicTrackedRace trackedRace, IRace tractracRace) {
TracTracCourseDesignUpdateHandler courseDesignHandler = new TracTracCourseDesignUpdateHandler(
tracTracUpdateURI, tracTracUsername, tracTracPassword, tracTracEventUuid,
raceDefinition.getId());
raceDefinition.getId(), tractracRace, this);
trackedRace.addCourseDesignChangedListener(courseDesignHandler);
TracTracStartTimeUpdateHandler startTimeHandler = new TracTracStartTimeUpdateHandler(
tracTracUpdateURI, tracTracUsername, tracTracPassword, tracTracEventUuid,
@@ -154,9 +154,9 @@ public class MetadataParserImpl implements MetadataParser {
if (name2 == null) {
name2 = controlPointName + " (2)";
}
final Serializable id1 = mark1UUID == null ? name1 : UUID.fromString(mark1UUID);
final Serializable id1 = mark1UUID == null ? controlPoint.getId().toString()+" (1)" : UUID.fromString(mark1UUID);
ControlPointMetaData mark1Metadata = new ControlPointMetaDataImpl(name1, type1, color1, shape1, pattern1, id1);
final Serializable id2 = mark2UUID == null ? name2 : UUID.fromString(mark2UUID);
final Serializable id2 = mark2UUID == null ? controlPoint.getId().toString()+" (2)" : UUID.fromString(mark2UUID);
ControlPointMetaData mark2Metadata = new ControlPointMetaDataImpl(name2, type2, color2, shape2, pattern2, id2);
result = Arrays.asList(new ControlPointMetaData[] { mark1Metadata, mark2Metadata });
} else {
@@ -15,6 +15,7 @@ import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Sideline;
@@ -146,6 +147,7 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<IControlRoute,
getDomainFactory().updateCourseWaypoints(existingRaceDefinitionForRace.getCourse(), ttControlPoints);
if (getTrackedRegatta().getExistingTrackedRace(existingRaceDefinitionForRace) == null) {
trackedRace = createTrackedRace(existingRaceDefinitionForRace, sidelines, tr->updateRaceTimes(tractracRace, tr));
addAllMarksFromCourseArea(trackedRace);
}
} catch (PatchFailedException e) {
logger.log(Level.SEVERE, "Internal error updating race course "+course+": "+e.getMessage());
@@ -160,13 +162,29 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<IControlRoute,
getTrackedRegatta(), tractracRace.getId(), tractracRace.getName(), competitorsAndDominantBoatClass.getA(),
competitorsAndDominantBoatClass.getB(), competitorBoats, course, sidelines, windStore, delayToLiveInMillis,
millisecondsOverWhichToAverageWind, raceDefinitionSetToUpdate, tracTracUpdateURI,
getTracTracEvent().getId(), tracTracUsername, tracTracPassword, useInternalMarkPassingAlgorithm, raceLogResolver, tr->updateRaceTimes(tractracRace, tr));
getTracTracEvent().getId(), tracTracUsername, tracTracPassword, useInternalMarkPassingAlgorithm, raceLogResolver, tr->updateRaceTimes(tractracRace, tr), tractracRace);
addAllMarksFromCourseArea(trackedRace);
if (getSimulator() != null) {
getSimulator().setTrackedRace(trackedRace);
}
}
}
/**
* For all marks associated with the course area of the {@link #tractracRace}'s {@link IRace#getCourseArea() course area} (if any; this
* was introduced with the TracAPI version 3.6.1) a mark track is created in the {@link TrackedRace}. This will let the {@link TrackedRace#getMarks}
* method return those, helping clients asking for "available" marks.
*/
private void addAllMarksFromCourseArea(DynamicTrackedRace trackedRace) {
for (final IControl tractracControlPoint : getDomainFactory().getControlsForCourseArea(getTracTracEvent(), tractracRace.getCourseArea())) {
final TracTracControlPoint ttcp = new ControlPointAdapter(tractracControlPoint);
final ControlPoint cp = getDomainFactory().getOrCreateControlPoint(ttcp);
for (final Mark mark : cp.getMarks()) {
trackedRace.getOrCreateTrack(mark);
}
}
}
private void updateRaceTimes(IRace tractracRace, DynamicTrackedRace trackedRace) {
final int liveDelayInSeconds = tractracRace.getLiveDelay();
long delayInMillis = liveDelayInSeconds * 1000;
@@ -225,7 +243,8 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<IControlRoute,
if (runAfterCreatingTrackedRace != null) {
runAfterCreatingTrackedRace.accept(trackedRace);
}
getDomainFactory().addTracTracUpdateHandlers(tracTracUpdateURI, getTracTracEvent().getId(), tracTracUsername, tracTracPassword, race, trackedRace);
getDomainFactory().addTracTracUpdateHandlers(tracTracUpdateURI, getTracTracEvent().getId(), tracTracUsername, tracTracPassword, race, trackedRace,
tractracRace);
return trackedRace;
}
@@ -6,13 +6,18 @@ import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.Iterator;
import java.util.logging.Logger;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.base.CourseBase;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.CourseDataImpl;
import com.sap.sailing.domain.tracking.CourseDesignChangedListener;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.ControlPointJsonSerializer;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.CourseBaseJsonSerializer;
@@ -20,16 +25,23 @@ import com.sap.sailing.server.gateway.serialization.coursedata.impl.CourseJsonSe
import com.sap.sailing.server.gateway.serialization.coursedata.impl.GateJsonSerializer;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.MarkJsonSerializer;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.WaypointJsonSerializer;
import com.sap.sse.common.Util;
import com.tractrac.model.lib.api.event.IRace;
import com.tractrac.model.lib.api.route.IControl;
public class TracTracCourseDesignUpdateHandler extends UpdateHandler implements CourseDesignChangedListener {
private final static String ACTION = "update_course";
private final static Logger logger = Logger.getLogger(TracTracCourseDesignUpdateHandler.class.getName());
private JsonSerializer<CourseBase> courseSerializer;
private final JsonSerializer<CourseBase> courseSerializer;
private final IRace tractracRace;
private final DomainFactory domainFactory;
public TracTracCourseDesignUpdateHandler(URI updateURI, String tracTracUsername, String tracTracPassword, Serializable tracTracEventId, Serializable raceId) {
public TracTracCourseDesignUpdateHandler(URI updateURI, String tracTracUsername, String tracTracPassword, Serializable tracTracEventId, Serializable raceId, IRace tractracRace, DomainFactory domainFactory) {
super(updateURI, ACTION, tracTracUsername, tracTracPassword, tracTracEventId, raceId);
this.domainFactory = domainFactory;
this.tractracRace = tractracRace;
this.courseSerializer = new CourseJsonSerializer(
new CourseBaseJsonSerializer(
new WaypointJsonSerializer(
@@ -39,12 +51,13 @@ public class TracTracCourseDesignUpdateHandler extends UpdateHandler implements
}
@Override
public void courseDesignChanged(CourseBase newCourseDesign) throws MalformedURLException, IOException {
public void courseDesignChanged(final CourseBase newCourseDesign) throws MalformedURLException, IOException {
if (!isActive()) {
logger.info("Not sending course update to TracTrac because no URL has been provided.");
return;
}
JSONObject serializedCourseDesign = courseSerializer.serialize(newCourseDesign);
final CourseBase newCourseDesignWithExistingControlPoints = replaceControlPointsByMatchingExistingControlPoints(newCourseDesign);
JSONObject serializedCourseDesign = courseSerializer.serialize(newCourseDesignWithExistingControlPoints);
String payload = serializedCourseDesign.toJSONString();
URL currentCourseDesignURL = buildUpdateURL();
logger.info("Using " + currentCourseDesignURL.toString() + " for the course update!");
@@ -66,4 +79,28 @@ public class TracTracCourseDesignUpdateHandler extends UpdateHandler implements
}
}
}
private CourseBase replaceControlPointsByMatchingExistingControlPoints(CourseBase courseDesign) {
final Iterable<IControl> candidates = domainFactory.getControlsForCourseArea(tractracRace.getEvent(), tractracRace.getCourseArea());
final CourseBase result = new CourseDataImpl(courseDesign.getName());
int zeroBasedPosition = 0;
boolean changed = false;
for (final Waypoint waypoint : courseDesign.getWaypoints()) {
if (Util.size(waypoint.getMarks()) > 1) {
final Iterator<Mark> markIter = waypoint.getMarks().iterator();
final Mark first = markIter.next();
final Mark second = markIter.next();
final IControl existingControl = domainFactory.getExistingControlWithTwoMarks(candidates, first, second);
if (existingControl == null) {
result.addWaypoint(zeroBasedPosition++, waypoint);
} else {
domainFactory.getBaseDomainFactory().createWaypoint(domainFactory.getOrCreateControlPoint(new ControlPointAdapter(existingControl)),
waypoint.getPassingInstructions());
}
} else {
result.addWaypoint(zeroBasedPosition++, waypoint);
}
}
return changed ? result : courseDesign;
}
}
@@ -26,14 +26,11 @@ public class GateDeserializer implements JsonDeserializer<ControlPointWithTwoMar
@Override
public ControlPointWithTwoMarks deserialize(JSONObject object) throws JsonDeserializationException {
JSONObject jsonLeftMark = (JSONObject) object.get(GateJsonSerializer.FIELD_LEFT);
JSONObject jsonRightMark = (JSONObject) object.get(GateJsonSerializer.FIELD_RIGHT);
Mark leftMark = markDeserializer.deserialize(jsonLeftMark);
Mark rightMark = markDeserializer.deserialize(jsonRightMark);
String gateName = (String) object.get(BaseControlPointJsonSerializer.FIELD_NAME);
ControlPointWithTwoMarks controlPoint = factory.createControlPointWithTwoMarks(leftMark, rightMark, gateName);
return controlPoint;
}
@@ -34,5 +34,4 @@ public class MarkDeserializer implements JsonDeserializer<Mark> {
Mark mark = factory.getOrCreateMark(id, name, type, color, shape, pattern);
return mark;
}
}