mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-26 23:46:36 +00:00
added sailingserver/expedition servlet which propagages all Expedition messages, valid or not, to the client
This commit is contained in:
+7
@@ -154,4 +154,11 @@ public interface ExpeditionMessage extends UDPMessage {
|
||||
TimePoint getTimePoint();
|
||||
|
||||
TimePoint getCreatedAt();
|
||||
|
||||
/**
|
||||
* The original text string as received from the Expedition program, including trailing checksum.
|
||||
*
|
||||
* @return for example <code>"#0,6,349.1,48,53.967217,49,10.888550,94,349.9,95,2.41*37"</code>
|
||||
*/
|
||||
String getOriginalMessage();
|
||||
}
|
||||
|
||||
-1
@@ -43,7 +43,6 @@ public class ExpeditionWindTracker implements ExpeditionListener, WindTracker {
|
||||
* @param receiver
|
||||
* receive wind data from this receiver by adding the new object as a listener to the receiver; when
|
||||
* calling {@link #stop}, this subscription will be removed again.
|
||||
* @param factory TODO
|
||||
*/
|
||||
public ExpeditionWindTracker(DynamicTrackedRace race, DeclinationService declinationService,
|
||||
UDPExpeditionReceiver receiver, ExpeditionWindTrackerFactory factory) {
|
||||
|
||||
+8
-4
@@ -19,7 +19,7 @@ import com.sap.sailing.expeditionconnector.impl.Activator;
|
||||
public class ExpeditionWindTrackerFactory implements WindTrackerFactory, BundleActivator {
|
||||
private static Logger logger = Logger.getLogger(ExpeditionWindTrackerFactory.class.getName());
|
||||
|
||||
private static WindTrackerFactory defaultInstance;
|
||||
private static ExpeditionWindTrackerFactory defaultInstance;
|
||||
|
||||
private static BundleContext defaultBundleContext;
|
||||
|
||||
@@ -40,7 +40,7 @@ public class ExpeditionWindTrackerFactory implements WindTrackerFactory, BundleA
|
||||
logger.info("Created "+getClass().getName()+" with default UDP port "+defaultPort);
|
||||
}
|
||||
|
||||
public synchronized static WindTrackerFactory getInstance() {
|
||||
public synchronized static ExpeditionWindTrackerFactory getInstance() {
|
||||
if (defaultInstance == null) {
|
||||
defaultInstance = new ExpeditionWindTrackerFactory();
|
||||
}
|
||||
@@ -57,14 +57,18 @@ public class ExpeditionWindTrackerFactory implements WindTrackerFactory, BundleA
|
||||
WindTracker result = windTrackers.get(race);
|
||||
if (result == null) {
|
||||
DynamicTrackedRace trackedRace = trackedEvent.getTrackedRace(race);
|
||||
UDPExpeditionReceiver receiver = getOrCreateWindReceiverForPort(defaultPort);
|
||||
UDPExpeditionReceiver receiver = getOrCreateWindReceiverOnDefaultPort();
|
||||
result = new ExpeditionWindTracker(trackedRace,
|
||||
correctByDeclination ? DeclinationService.INSTANCE : null, receiver, this);
|
||||
windTrackers.put(race, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public UDPExpeditionReceiver getOrCreateWindReceiverOnDefaultPort() throws SocketException {
|
||||
return getOrCreateWindReceiverForPort(defaultPort);
|
||||
}
|
||||
|
||||
private synchronized UDPExpeditionReceiver getOrCreateWindReceiverForPort(int port) throws SocketException {
|
||||
UDPExpeditionReceiver receiver = windReceivers.get(port);
|
||||
if (receiver == null) {
|
||||
|
||||
+8
-1
@@ -13,7 +13,7 @@ import java.net.InetAddress;
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
public class UDPMirror {
|
||||
public class UDPMirror implements Runnable {
|
||||
|
||||
/**
|
||||
* @param args 0: the port to listen to; 2*i-1, 2*i for i>0: host/port to which to forward
|
||||
@@ -53,6 +53,13 @@ public class UDPMirror {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for an inbound TCP connection
|
||||
*/
|
||||
public void run() {
|
||||
|
||||
}
|
||||
|
||||
private static void usage() {
|
||||
System.out.println("Usage: java "+UDPMirror.class.getName()+" [-v] <listeningport> hostname1 port1 [hostname2 port2]*");
|
||||
|
||||
+10
-2
@@ -22,6 +22,7 @@ import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl;
|
||||
import com.sap.sailing.expeditionconnector.ExpeditionMessage;
|
||||
|
||||
public class ExpeditionMessageImpl implements ExpeditionMessage {
|
||||
private final String originalMessage;
|
||||
private final int boatID;
|
||||
private final Map<Integer, Double> values;
|
||||
private final boolean valid;
|
||||
@@ -40,11 +41,12 @@ public class ExpeditionMessageImpl implements ExpeditionMessage {
|
||||
* <code>values</code>, the {@link System#currentTimeMillis() current time} is used as the message's
|
||||
* {@link #getTimePoint() time point}.
|
||||
*/
|
||||
public ExpeditionMessageImpl(int boatID, Map<Integer, Double> values, boolean valid) {
|
||||
public ExpeditionMessageImpl(int boatID, Map<Integer, Double> values, boolean valid, String originalMessage) {
|
||||
this.boatID = boatID;
|
||||
// ensure that nobody can manipulate the map used by this message object from outside
|
||||
this.values = new HashMap<Integer, Double>(values);
|
||||
this.valid = valid;
|
||||
this.originalMessage = originalMessage;
|
||||
this.createdAtMillis = System.currentTimeMillis();
|
||||
if (hasValue(ID_GPS_TIME)) {
|
||||
timePoint = new MillisecondsTimePoint((long)
|
||||
@@ -60,7 +62,7 @@ public class ExpeditionMessageImpl implements ExpeditionMessage {
|
||||
* a non-<code>null</code> default time point to use in case the message received does not carry a time
|
||||
* stamp
|
||||
*/
|
||||
public ExpeditionMessageImpl(int boatID, Map<Integer, Double> values, boolean valid, TimePoint defaultTimePoint) {
|
||||
public ExpeditionMessageImpl(int boatID, Map<Integer, Double> values, boolean valid, TimePoint defaultTimePoint, String originalMessage) {
|
||||
if (defaultTimePoint == null) {
|
||||
throw new IllegalArgumentException("defaultTimePoint for ExpeditionMessageImpl constructor must not be null");
|
||||
}
|
||||
@@ -68,6 +70,7 @@ public class ExpeditionMessageImpl implements ExpeditionMessage {
|
||||
// ensure that nobody can manipulate the map used by this message object from outside
|
||||
this.values = new HashMap<Integer, Double>(values);
|
||||
this.valid = valid;
|
||||
this.originalMessage = originalMessage;
|
||||
this.createdAtMillis = System.currentTimeMillis();
|
||||
if (hasValue(ID_GPS_TIME)) {
|
||||
timePoint = new MillisecondsTimePoint((long)
|
||||
@@ -78,6 +81,11 @@ public class ExpeditionMessageImpl implements ExpeditionMessage {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOriginalMessage() {
|
||||
return originalMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
|
||||
+3
-3
@@ -50,11 +50,11 @@ public class ExpeditionMessageParser implements UDPMessageParser<ExpeditionMessa
|
||||
}
|
||||
int checksum = Integer.valueOf(m.group(m.groupCount()), 16);
|
||||
valid = valid && checksumOk(checksum, packetAsString);
|
||||
ExpeditionMessageImpl result;
|
||||
ExpeditionMessage result;
|
||||
if (defaultForMessageTimePoint == null) {
|
||||
result = new ExpeditionMessageImpl(boatID, values, valid);
|
||||
result = new ExpeditionMessageImpl(boatID, values, valid, packetAsString);
|
||||
} else {
|
||||
result = new ExpeditionMessageImpl(boatID, values, valid, defaultForMessageTimePoint);
|
||||
result = new ExpeditionMessageImpl(boatID, values, valid, defaultForMessageTimePoint, packetAsString);
|
||||
}
|
||||
if (result.hasValue(ExpeditionMessage.ID_GPS_TIME)) {
|
||||
// an original GPS time stamp; then remember the difference between now and the time stamp
|
||||
|
||||
@@ -9,7 +9,8 @@ Require-Bundle: com.sap.sailing.server,
|
||||
com.sap.sailing.domain,
|
||||
com.sap.sailing.domain.tractracadapter,
|
||||
com.sap.sailing.domain.swisstimingadapter,
|
||||
com.sap.sailing.domain.common
|
||||
com.sap.sailing.domain.common,
|
||||
com.sap.sailing.expeditionconnector
|
||||
Import-Package: junit.framework;version="4.8.1",
|
||||
org.junit;version="4.8.1",
|
||||
com.tractrac.clientmodule,
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.sap.sailing.server.test;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ExpeditionThroughHttpPostServletTest {
|
||||
@Ignore // To run, the OSGi-based Jetty needs to run and listen on port 8888"
|
||||
@Test
|
||||
public void testConnectDisconnect() throws IOException, InterruptedException {
|
||||
int jettyPort = 8888;
|
||||
URL url = new URL("http://localhost:"+jettyPort+"/sailingserver/expedition");
|
||||
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setChunkedStreamingMode(/* chunklen */ 8192);
|
||||
connection.setDoOutput(true);
|
||||
connection.setRequestMethod("POST");
|
||||
PrintWriter requestWriter = new PrintWriter(connection.getOutputStream());
|
||||
requestWriter.println("<ping>");
|
||||
requestWriter.flush();
|
||||
Thread reader = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
String line = br.readLine();
|
||||
while (line != null) {
|
||||
System.out.println(line);
|
||||
line = br.readLine();
|
||||
}
|
||||
br.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}, "ExpeditionThroughHttpPostServletTest reader");
|
||||
reader.start();
|
||||
reader.join();
|
||||
requestWriter.close();
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,8 @@ Require-Bundle: com.sap.sailing.domain,
|
||||
com.sap.sailing.domain.swisstimingadapter,
|
||||
com.sap.sailing.domain.persistence,
|
||||
com.sap.sailing.domain.swisstimingadapter.persistence,
|
||||
com.sap.sailing.domain.common
|
||||
com.sap.sailing.domain.common,
|
||||
com.sap.sailing.udpconnector
|
||||
Bundle-ClassPath: .
|
||||
Export-Package: com.sap.sailing.server,
|
||||
com.sap.sailing.server.impl;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
|
||||
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
|
||||
|
||||
<servlet>
|
||||
<servlet-name>AdminApp</servlet-name>
|
||||
<servlet-class>com.sap.sailing.server.impl.AdminApp</servlet-class>
|
||||
@@ -12,6 +13,7 @@
|
||||
<servlet-name>AdminApp</servlet-name>
|
||||
<url-pattern>/admin</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>ModeratorApp</servlet-name>
|
||||
<servlet-class>com.sap.sailing.server.impl.ModeratorApp</servlet-class>
|
||||
@@ -20,4 +22,13 @@
|
||||
<servlet-name>ModeratorApp</servlet-name>
|
||||
<url-pattern>/moderator</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>ExpeditionThroughHttpPostServlet</servlet-name>
|
||||
<servlet-class>com.sap.sailing.server.impl.ExpeditionThroughHttpPostServlet</servlet-class>
|
||||
</servlet>
|
||||
<servlet-mapping>
|
||||
<servlet-name>ExpeditionThroughHttpPostServlet</servlet-name>
|
||||
<url-pattern>/expedition</url-pattern>
|
||||
</servlet-mapping>
|
||||
</web-app>
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.sap.sailing.domain.tracking.WindStore;
|
||||
import com.sap.sailing.domain.tractracadapter.DomainFactory;
|
||||
import com.sap.sailing.domain.tractracadapter.RaceRecord;
|
||||
import com.sap.sailing.domain.tractracadapter.TracTracRaceTracker;
|
||||
import com.sap.sailing.expeditionconnector.ExpeditionListener;
|
||||
|
||||
/**
|
||||
* An OSGi service that can be used to track boat races using a TracTrac connector that pushes
|
||||
@@ -238,4 +239,8 @@ public interface RacingEventService extends TrackedEventRegistry {
|
||||
|
||||
void updateStoredLeaderboardGroup(LeaderboardGroup leaderboardGroup);
|
||||
|
||||
void addExpeditionListener(ExpeditionListener listener, boolean validMessagesOnly) throws SocketException;
|
||||
|
||||
void removeExpeditionListener(ExpeditionListener listener) throws SocketException;
|
||||
|
||||
}
|
||||
|
||||
Executable
+87
@@ -0,0 +1,87 @@
|
||||
package com.sap.sailing.server.impl;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.sap.sailing.expeditionconnector.ExpeditionListener;
|
||||
import com.sap.sailing.expeditionconnector.ExpeditionMessage;
|
||||
import com.sap.sailing.server.RacingEventService;
|
||||
import com.sap.sailing.server.Servlet;
|
||||
|
||||
public class ExpeditionThroughHttpPostServlet extends Servlet {
|
||||
private static final long timeoutInMilliseconds = 60000;
|
||||
private long timeInMillisOfLastExpeditionMessageReceived;
|
||||
|
||||
private static final long serialVersionUID = 6034769972654796465L;
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
RacingEventService service = getService();
|
||||
final PrintWriter writer = resp.getWriter();
|
||||
Thread pingPongHandler = new Thread(new PingPongHandler(req.getInputStream(), writer), getClass().getName()
|
||||
+ " PingPongHandler " + Thread.currentThread().getId());
|
||||
service.addExpeditionListener(new ExpeditionListener() {
|
||||
@Override
|
||||
public void received(ExpeditionMessage message) {
|
||||
timeInMillisOfLastExpeditionMessageReceived = System.currentTimeMillis();
|
||||
synchronized (writer) {
|
||||
writer.println(message.getOriginalMessage());
|
||||
writer.flush();
|
||||
}
|
||||
}
|
||||
}, /* validMessagesOnly */ false);
|
||||
pingPongHandler.start();
|
||||
try {
|
||||
Thread.sleep(timeoutInMilliseconds);
|
||||
while (System.currentTimeMillis()-timeInMillisOfLastExpeditionMessageReceived < timeoutInMilliseconds) {
|
||||
Thread.sleep(timeInMillisOfLastExpeditionMessageReceived + timeoutInMilliseconds - System.currentTimeMillis());
|
||||
}
|
||||
pingPongHandler.join();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads from the request input stream. If a "<ping>\n" message comes along, a "<pong>\n" message will be sent back.
|
||||
* This will allow a client to regularly check live-ness of the connection and reconnect if needed.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*/
|
||||
private class PingPongHandler implements Runnable {
|
||||
private final InputStream requestInputStream;
|
||||
private final PrintWriter responseWriter;
|
||||
|
||||
public PingPongHandler(InputStream requestInputStream, PrintWriter responseWriter) {
|
||||
super();
|
||||
this.requestInputStream = requestInputStream;
|
||||
this.responseWriter = responseWriter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(requestInputStream));
|
||||
try {
|
||||
String line = br.readLine();
|
||||
while (line != null) {
|
||||
if (line.equals("<ping>")) {
|
||||
synchronized (responseWriter) {
|
||||
responseWriter.println("<pong>");
|
||||
responseWriter.flush();
|
||||
}
|
||||
}
|
||||
line = br.readLine();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-2
@@ -60,13 +60,14 @@ import com.sap.sailing.domain.tracking.TrackedEvent;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.WindStore;
|
||||
import com.sap.sailing.domain.tracking.WindTracker;
|
||||
import com.sap.sailing.domain.tracking.WindTrackerFactory;
|
||||
import com.sap.sailing.domain.tracking.impl.DynamicTrackedEventImpl;
|
||||
import com.sap.sailing.domain.tractracadapter.DomainFactory;
|
||||
import com.sap.sailing.domain.tractracadapter.JSONService;
|
||||
import com.sap.sailing.domain.tractracadapter.RaceRecord;
|
||||
import com.sap.sailing.domain.tractracadapter.Receiver;
|
||||
import com.sap.sailing.expeditionconnector.ExpeditionListener;
|
||||
import com.sap.sailing.expeditionconnector.ExpeditionWindTrackerFactory;
|
||||
import com.sap.sailing.expeditionconnector.UDPExpeditionReceiver;
|
||||
import com.sap.sailing.server.RacingEventService;
|
||||
|
||||
public class RacingEventServiceImpl implements RacingEventService, EventFetcher, RaceFetcher {
|
||||
@@ -82,7 +83,7 @@ public class RacingEventServiceImpl implements RacingEventService, EventFetcher,
|
||||
|
||||
private final com.sap.sailing.domain.swisstimingadapter.DomainFactory swissTimingDomainFactory;
|
||||
|
||||
private final WindTrackerFactory windTrackerFactory;
|
||||
private final ExpeditionWindTrackerFactory windTrackerFactory;
|
||||
|
||||
protected final Map<String, Event> eventsByName;
|
||||
|
||||
@@ -733,5 +734,17 @@ public class RacingEventServiceImpl implements RacingEventService, EventFetcher,
|
||||
public void updateStoredLeaderboardGroup(LeaderboardGroup leaderboardGroup) {
|
||||
mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExpeditionListener(ExpeditionListener listener, boolean validMessagesOnly) throws SocketException {
|
||||
UDPExpeditionReceiver receiver = windTrackerFactory.getOrCreateWindReceiverOnDefaultPort();
|
||||
receiver.addListener(listener, validMessagesOnly);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeExpeditionListener(ExpeditionListener listener) throws SocketException {
|
||||
UDPExpeditionReceiver receiver = windTrackerFactory.getOrCreateWindReceiverOnDefaultPort();
|
||||
receiver.removeListener(listener);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user