added sailingserver/expedition servlet which propagages all Expedition messages, valid or not, to the client

This commit is contained in:
Axel Uhl
2012-02-14 13:59:38 +01:00
parent dd34c6ac49
commit 099418ca21
13 changed files with 203 additions and 15 deletions
@@ -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();
}
@@ -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) {
@@ -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) {
@@ -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]*");
@@ -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;
@@ -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,
@@ -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;
}
@@ -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();
}
}
}
}
@@ -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);
}
}