Merge branch 'racecommittee_app' into racecommittee_app_startphases

Conflicts:
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties
	java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java
This commit is contained in:
Lukas Niemeier committed 2013-11-29 18:24:13 +01:00
commit 04e8671a11
189 files changed
+3651 -875319

No files matched your search

+26 -26
View File
@@ -51,7 +51,11 @@ fi
cd $PROJECT_HOME
active_branch=$(git symbolic-ref -q HEAD)
active_branch=`basename $active_branch`
if [[ $active_branch == "" ]]; then
active_branch="unknown"
else
active_branch=`basename $active_branch`
fi
HEAD_SHA=$(git show-ref --head -s | head -1)
HEAD_DATE=$(date "+%Y%m%d%H%M")
@@ -122,6 +126,7 @@ fi
echo PROJECT_HOME is $PROJECT_HOME
echo SERVERS_HOME is $SERVERS_HOME
echo BRANCH is $active_branch
echo VERSION is $VERSION_INFO
options=':bgtocprm:n:l:s:w:u'
while getopts $options option
@@ -129,7 +134,7 @@ do
case $option in
g) gwtcompile=0;;
t) testing=0;;
b) onegwtpermutationonly=1;;
b) onegwtpermutationonly=1;;
o) offline=1;;
c) clean="";;
p) proxy=1;;
@@ -164,22 +169,25 @@ if [[ "$@" == "release" ]]; then
fi
RELEASE_NOTES=""
echo ""
echo "Please provide me with some notes about this release. You can add more than"
echo "one line. Please include major changes or new features. After your notes I will"
echo "also include the commits of the last 4 weeks. You can save and quit by hitting ctrl+d."
while read -e -p "> " line; do
RELEASE_NOTES="$RELEASE_NOTES\n$line"
done
COMMIT_WEEK_COUNT=4
if [ $suppress_confirmation -eq 0 ]; then
echo ""
echo "Please provide me with some notes about this release. You can add more than"
echo "one line. Please include major changes or new features. After your notes I will"
echo "also include the commits of the last 4 weeks. You can save and quit by hitting ctrl+d."
while read -e -p "> " line; do
RELEASE_NOTES="$RELEASE_NOTES\n$line"
done
if [[ $RELEASE_NOTES == "" ]]; then
echo -e "\nCome on - I can not release without at least some notes about this release!"
exit
if [[ $RELEASE_NOTES == "" ]]; then
echo -e "\nCome on - I can not release without at least some notes about this release!"
exit
fi
echo -e "\nThank you! One last thing..."
echo "How many weeks of commits do you want to include (0=No commits)?"
read -p "> " -e COMMIT_WEEK_COUNT
fi
echo -e "\nThank you! One last thing..."
echo "How many weeks of commits do you want to include (0=No commits)?"
read -p "> " -e COMMIT_WEEK_COUNT
mkdir -p $PROJECT_HOME/dist
mkdir -p $PROJECT_HOME/build
@@ -201,6 +209,7 @@ if [[ "$@" == "release" ]]; then
cp -v $PROJECT_HOME/java/target/start $ACDIR/
cp -v $PROJECT_HOME/java/target/stop $ACDIR/
cp -v $PROJECT_HOME/java/target/status $ACDIR/
cp -v $PROJECT_HOME/java/target/updateEC2Instance.sh $ACDIR/
cp -v $PROJECT_HOME/java/target/shouldIBuildOrShouldIGo.sh $ACDIR/
cp -v $PROJECT_HOME/java/target/env.sh $ACDIR/
@@ -220,18 +229,8 @@ if [[ "$@" == "release" ]]; then
cp -rv $PROJECT_HOME/configuration/native-libraries $ACDIR/
cp -v $PROJECT_HOME/configuration/buildAndUpdateProduct.sh $ACDIR/
# make sure to save the information from env.sh
. $ACDIR/env.sh
echo "$VERSION_INFO System:" > $ACDIR/configuration/jetty/version.txt
sed -i "/mongo.host/d" "$ACDIR/configuration/config.ini"
sed -i "/mongo.port/d" "$ACDIR/configuration/config.ini"
sed -i "/expedition.udp.port/d" "$ACDIR/configuration/config.ini"
sed -i "/replication.exchangeName/d" "$ACDIR/configuration/config.ini"
sed -i "/replication.exchangeHost/d" "$ACDIR/configuration/config.ini"
sed -i "s/^.*jetty.port.*$/<Set name=\"port\"><Property name=\"jetty.port\" default=\"$SERVER_PORT\"\/><\/Set>/g" "$ACDIR/configuration/jetty/etc/jetty-selector.xml"
if [[ $OSGI_BUNDLE_NAME != "" ]]; then
SIMPLE_VERSION_INFO=$OSGI_BUNDLE_NAME
fi
@@ -525,6 +524,7 @@ if [[ "$@" == "install" ]] || [[ "$@" == "all" ]]; then
cp -v $PROJECT_HOME/java/target/start $ACDIR/
cp -v $PROJECT_HOME/java/target/stop $ACDIR/
cp -v $PROJECT_HOME/java/target/status $ACDIR/
cp -v $PROJECT_HOME/java/target/updateEC2Instance.sh $ACDIR/
cp -v $PROJECT_HOME/java/target/shouldIBuildOrShouldIGo.sh $ACDIR/
if [ ! -f "$ACDIR/env.sh" ]; then
+4 -4
View File
@@ -1,12 +1,12 @@
import socket, sys
if len(sys.argv) <= 2:
print "Please provide ip and mode (TCP, UDP)"
if len(sys.argv) <= 3:
print "Please provide ip, port and mode (TCP, UDP)"
sys.exit(1)
UDP_IP = sys.argv[1]
UDP_PORT = 2013
MODE = sys.argv[2]
UDP_PORT = int(sys.argv[2])
MODE = sys.argv[3]
MESSAGE = "Hello, World!"
print "Target IP:", UDP_IP
+3 -3
View File
@@ -2,12 +2,12 @@ import socket
import sys
if len(sys.argv) <= 2:
print "Please provide an IP address and mode"
print "Please provide an IP, port and mode (UDP, TCP)"
sys.exit(1)
UDP_IP = sys.argv[1]
MODE = sys.argv[2]
UDP_PORT = 2013
UDP_PORT = sys.argv[2]
MODE = sys.argv[3]
print "Listening on %s port %s (%s)" % (UDP_IP, UDP_PORT, MODE)
if MODE == 'UDP':
-18
View File
@@ -265,28 +265,10 @@ into one in the future.
can be obtained; the resulting document includes the
parameter and simulation URLs for the individual
races that are part of the event/regatta.
action=addevent: starts tracking all races described in the document received
from the eventJSONURL to the server instance.
eventJSONURL: the URL from where metadata about the entire regatta
can be obtained; the resulting document includes the
parameter and simulation URLs for the individual
races that are part of the event/regatta.
liveURI: the live data URI for the TracTrac server
storedURI: the stored data URI for the TracTrac server; usually
the port number of this URI is that of liveURI + 1
action=stopregatta: stops tracking of all races of the regatta; this
also stops all wind tracking for the races of the regatta
regattaname: name of the regatta, as returned by the "listevents"
action on the /moderator servlet
action=addrace: starts tracking a single race; if other races of
the same event/regatta are already being tracked,
the race is added to the regatta
paramURL: the URL from where metadata about the race
can be obtained; the resulting document includes
UUIDs of waypoints, competitors, etc.
liveURI: the live data URI for the TracTrac server
storedURI: the stored data URI for the TracTrac server; usually
the port number of this URI is that of liveURI + 1
action=stoprace: stops tracking of a single race, also stopping the
wind trackers that were started for this race; other
races of the same regatta that were being tracked will
-874580
View File
File diff suppressed because it is too large. Load diff
+1
View File
@@ -27,6 +27,7 @@ Like businesses, sailors need the latest information to make strategic decisions
* [[Mobile Development|wiki/mobile-development]]
* Information for Webdesigners
* [[Layout repository|wiki/webdesign]]
* [[Create boat graphics for the 2D race viewer|wiki/boatGraphicsSVG]]
* General Information
* [[Architecture and Infrastructure|wiki/architecture-and-infrastructure]]
* [[Sailing Domain Algorithms|wiki/sailing-domain-algorithms]]
@@ -1,4 +1,4 @@
package com.sap.sailing.domain.base.impl;
package com.sap.sailing.domain.common.impl;
import com.sap.sailing.domain.common.AbstractSpeedImpl;
import com.sap.sailing.domain.common.Speed;
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>com.google.gwt.user.osgi</name>
<name>com.sap.sailing.domain.igtimiadapter.test</name>
<comment></comment>
<projects>
</projects>
@@ -0,0 +1,7 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.7
@@ -0,0 +1,3 @@
eclipse.preferences.version=1
pluginProject.extensions=false
resolve.requirebundle=false
@@ -0,0 +1,10 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Fragment-Host: com.sap.sailing.domain.igtimiadapter
Bundle-Name: Test
Bundle-SymbolicName: com.sap.sailing.domain.igtimiadapter.test
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Require-Bundle: org.junit4;bundle-version="4.8.2",
com.sap.sailing.domain.common
@@ -0,0 +1,4 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>root</artifactId>
<groupId>com.sap.sailing</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.sap.sailing.domain.igtimiadapter.test</artifactId>
<packaging>eclipse-test-plugin</packaging>
</project>
@@ -0,0 +1,108 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import java.util.Iterator;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.igtimiadapter.datatypes.AntHrm;
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
import com.sap.sailing.domain.igtimiadapter.datatypes.GpsLatLong;
import com.sap.sailing.domain.igtimiadapter.datatypes.GpsQualitySatCount;
public class FixFactoryTest {
private static final String demoJson = "{\n"+
"\"EA-AK-AAAG\": {\n"+
"\"1\": {\n"+
"\"1\": [\n"+
"-160.123,\n"+
"-160.1233\n"+
"],\n"+
"\"2\": [\n"+
"40.43,\n"+
"40.44\n"+
"],\n"+
"\"t\": [\n"+
"12345677000,\n"+
"12345677500\n"+
"]\n"+
"},\n"+
"\"3\": {\n"+
"\"1\": [\n"+
"21\n"+
"],\n"+
"\"t\": [\n"+
"12345677500\n"+
"]\n"+
"}\n"+
"},\n"+
"\"EA-AK-AAAH\": {\n"+
"\"1\": {\n"+
"\"1\": [\n"+
"-160.12\n"+
"],\n"+
"\"2\": [\n"+
"40.434\n"+
"],\n"+
"\"t\": [\n"+
"12345677000\n"+
"]\n"+
"},\n"+
"\"13:100\": {\n"+
"\"1\": [\n"+
"60\n"+
"],\n"+
"\"t\": [\n"+
"12345678000\n"+
"]\n"+
"},\n"+
"\"13:201\": {\n"+
"\"1\": [\n"+
"119,\n"+
"120\n"+
"],\n"+
"\"t\": [\n"+
"12345677000,\n"+
"12345677500\n"+
"]\n"+
"}\n"+
"}\n"+
"}";
private JSONObject json;
@Before
public void setUp() throws ParseException {
json = (JSONObject) new JSONParser().parse(demoJson);
}
@Test
public void testFixProduction() {
FixFactory fixFactory = new FixFactory();
Iterable<Fix> fixes = fixFactory.createFixes(json);
assertFalse(Util.isEmpty(fixes));
final Iterator<Fix> fixIter = fixes.iterator();
GpsLatLong firstFix = (GpsLatLong) fixIter.next();
assertEquals(new MillisecondsTimePoint(12345677000l), firstFix.getTimePoint());
assertEquals(-160.123, firstFix.getPosition().getLngDeg(), 0.0000001);
assertEquals(40.43, firstFix.getPosition().getLatDeg(), 0.00000001);
GpsLatLong secondFix = (GpsLatLong) fixIter.next();
assertEquals(new MillisecondsTimePoint(12345677500l), secondFix.getTimePoint());
assertEquals(-160.1233, secondFix.getPosition().getLngDeg(), 0.0000001);
assertEquals(40.44, secondFix.getPosition().getLatDeg(), 0.00000001);
GpsQualitySatCount satCount = (GpsQualitySatCount) fixIter.next();
assertEquals(21, satCount.getSatCount());
fixIter.next();
AntHrm hrm = (AntHrm) fixIter.next();
assertEquals("100", hrm.getSensor().getDeviceId());
assertEquals(60, hrm.getHeartRate());
}
}
@@ -0,0 +1,105 @@
package com.sap.sailing.domain.igtimiadapter.oauth;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.http.client.ClientProtocolException;
import org.json.simple.parser.ParseException;
import org.junit.Test;
import org.xml.sax.SAXException;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.igtimiadapter.Account;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
import com.sap.sailing.domain.igtimiadapter.Permission;
import com.sap.sailing.domain.igtimiadapter.Resource;
import com.sap.sailing.domain.igtimiadapter.User;
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
import com.sap.sailing.domain.igtimiadapter.datatypes.Type;
import com.sap.sailing.domain.igtimiadapter.impl.Activator;
import com.sap.sailing.domain.igtimiadapter.impl.ClientImpl;
import com.sap.sailing.domain.igtimiadapter.impl.IgtimiConnectionFactoryImpl;
public class SignInTest {
private static final Logger logger = Logger.getLogger(SignInTest.class.getName());
@Test
public void testSimpleSignIn() throws ClientProtocolException, IOException, IllegalStateException,
ParserConfigurationException, SAXException, ClassNotFoundException, InstantiationException,
IllegalAccessException, ClassCastException, ParseException {
// use the credentials of "Another Test App"
final ClientImpl testAppClient = new ClientImpl("7fcdd217e0aa16090edb4ad55b09ec43b2021090e209541fc9b7003c2a2b70c6",
"aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a", "http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback");
final IgtimiConnectionFactoryImpl igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient);
final String code = igtimiConnectionFactory.authorizeAndReturnAuthorizedCode("axel.uhl@gmx.de", "123456");
logger.info("Igtimi OAuth code is "+code);
assertNotNull(code);
Account account = igtimiConnectionFactory.obtainAccessTokenFromAuthorizationCode(code);
assertEquals("axel.uhl@gmx.de", account.getUser().getEmail());
}
@Test
public void testSimpleAuthorizeForAppNotYetAuthorized() throws ClientProtocolException, IOException,
IllegalStateException, ParserConfigurationException, SAXException, ClassNotFoundException,
InstantiationException, IllegalAccessException, ClassCastException {
// use the credentials of "Another Test App"
final ClientImpl testAppClient = new ClientImpl("a4cecd8593e12d43a03433a6db0eea243a411749f93c278dce6a26d4804eebd2",
"4d66022d1ec3e2991f8053514495b61cc076ff02d664f0dc8f3df9150c3864ef", "http://1.2.3.4");
final String code = new IgtimiConnectionFactoryImpl(testAppClient).authorizeAndReturnAuthorizedCode("axel.uhl@gmx.de", "123456");
logger.info("Igtimi OAuth code is "+code);
assertNotNull(code);
}
@Test
public void testAddToken() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory();
Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9");
assertEquals("axel.uhl@gmx.de", account.getUser().getEmail());
assertSame(account, connectionFactory.getAccountByEmail("axel.uhl@gmx.de"));
}
@Test
public void testGetUsers() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory();
Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9");
IgtimiConnection connection = connectionFactory.connect(account);
Iterable<User> users = connection.getUsers();
assertEquals(1, Util.size(users));
assertEquals(account.getUser().getId(), users.iterator().next().getId());
}
@Test
public void testGetResources() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory();
Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9");
IgtimiConnection connection = connectionFactory.connect(account);
Iterable<Resource> resources = connection.getResources(Permission.read, /* start time */ null, /* end time */ null,
/* serial numbers */ Collections.singleton("GA-EN-AAEJ"), /* stream IDs */ null);
assertTrue(resources.iterator().hasNext());
}
@Test
public void testGetResourceData() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory();
Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9");
IgtimiConnection connection = connectionFactory.connect(account);
Map<Type, Double> typesAndCompression = new HashMap<>();
typesAndCompression.put(Type.gps_latlong, 0.0);
Iterable<Fix> data = connection.getResourceData(new MillisecondsTimePoint(1384420883000l),
new MillisecondsTimePoint(1384421639000l), Collections.singleton("GA-EN-AAEJ"), typesAndCompression);
assertTrue(data.iterator().hasNext());
}
}
@@ -7,8 +7,15 @@ Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Require-Bundle: org.json.simple;bundle-version="1.1.0",
com.sun.jersey;bundle-version="1.17.0",
org.objectweb.asm;bundle-version="3.3.1"
org.objectweb.asm;bundle-version="3.3.1",
org.apache.httpcomponents.httpclient;bundle-version="4.2.5",
org.apache.httpcomponents.httpcore;bundle-version="4.0.0",
org.apache.commons.logging;bundle-version="1.1.1",
com.sap.sailing.domain.common
Import-Package: javax.ws.rs;version="1.1.1",
javax.ws.rs.core;version="1.1.1",
javax.ws.rs.ext;version="1.1.1"
javax.ws.rs.ext;version="1.1.1",
org.apache.commons.lang,
org.osgi.framework;version="1.7.0"
Web-ContextPath: /igtimi
Bundle-Activator: com.sap.sailing.domain.igtimiadapter.impl.Activator
@@ -0,0 +1,5 @@
package com.sap.sailing.domain.igtimiadapter;
public interface Account {
User getUser();
}
@@ -3,19 +3,20 @@ package com.sap.sailing.domain.igtimiadapter;
import javax.security.auth.callback.Callback;
/**
* Represents what the Igtimi API calls a "client." In particular, a client has an ID and a secret. The Igtimi
* connector of which this interface is a part acts as a client. A client needs to be authorized by a user to
* be allowed to access the user's data via the Igtimi web services API. Authorization works by directing the
* user to a URL using his/her user agent (usually a browser) which is constructed by the
* {@link IgtimiConnectorFactory} and encodes a callback URL that leads to the {@link Callback} servlet.
* The servlet records the access token that can be used for Igtimi web service API calls that require
* access to the authenticated user's data.
* Represents what the Igtimi API or OAuth calls a "client." In particular, a client has an ID and a secret. The Igtimi
* connector of which this interface is a part acts as a client. A client needs to be authorized by a user to be allowed
* to access the user's data via the Igtimi web services API. Authorization works by directing the user to a URL using
* his/her user agent (usually a browser) which is constructed by the {@link IgtimiConnectionFactory} and encodes a
* callback URL that leads to the {@link Callback} servlet. The servlet records the access token that can be used for
* Igtimi web service API calls that require access to the authenticated user's data.
*
* @author Axel Uhl (d043530)
*
*
*/
public interface Client {
String getId();
String getSecret();
String getRedirectUri();
}
@@ -0,0 +1,39 @@
package com.sap.sailing.domain.igtimiadapter;
import java.io.IOException;
import java.util.Map;
import org.apache.http.client.ClientProtocolException;
import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
import com.sap.sailing.domain.igtimiadapter.datatypes.Type;
/**
* A connection to the Igtimi system for one {@link Client} and one {@link Account}.
*
* @author Axel Uhl (d043530)
*/
public interface IgtimiConnection {
Iterable<User> getUsers() throws IllegalStateException, ClientProtocolException, IOException, ParseException;
/**
* @param startTime optional; may be <code>null</code>
* @param endTime optional; may be <code>null</code>
* @param serialNumbers optional (may be <code>null</code>) if <code>permission</code> is {@link Permission#modify}
* @param streamIds optional; may be <code>null</code>
*/
Iterable<Resource> getResources(Permission permission, TimePoint startTime, TimePoint endTime,
Iterable<String> serialNumbers, Iterable<String> streamIds) throws IllegalStateException,
ClientProtocolException, IOException, ParseException;
/**
* All arguments are mandatory.
*/
Iterable<Fix> getResourceData(TimePoint startTime, TimePoint endTime, Iterable<String> serialNumbers,
Map<Type, Double> typeAndCompression) throws IllegalStateException, ClientProtocolException, IOException,
ParseException;
}
@@ -0,0 +1,67 @@
package com.sap.sailing.domain.igtimiadapter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import org.apache.http.client.ClientProtocolException;
import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.datatypes.Type;
import com.sap.sailing.domain.igtimiadapter.oauth.AuthorizationCallback;
/**
* A connection factory is bound to a {@link Client} and allows that client to obtain a connection to the Igtimi
* services on behalf of an {@link Account} for which an authorization token must have been
* {@link #registerAccountForWhichClientIsAuthorized(String) registered} with this factory previously.
*
* @author Axel Uhl (d043530)
*/
public interface IgtimiConnectionFactory {
/**
* Obtains a URL that a user agent (e.g., a web browser) can be sent to in order to allow that user to authenticate
* and then authorize this factory's {@link Client} for accessing the user's Igtimi data. The URL is chosen such that
* it redirects to the {@link AuthorizationCallback} with the <code>code</code> response type.
*/
String getAuthorizationUrl();
/**
* Using the "account" service, retrieves the {@link Account} information for this access token and stores the token
* that authorizes this factory's client to access the account together with the {@link Account} data. This information is
* used when a caller wants to {@link #connect(Account)} to a specific account on behalf of the {@link Client} to
* which this factory belongs.
* @return
*/
Account registerAccountForWhichClientIsAuthorized(String accessToken) throws ClientProtocolException,
IllegalStateException, IOException, ParseException;
/**
* Matches <code>eMail</code> with the e-mail information retrieved from the "account" service earlier when an access
* token was registered.
*/
Account getAccountByEmail(String eMail);
IgtimiConnection connect(Account account);
/**
* Uses the /oauth/token service to obtain and {@link #registerAccountForWhichClientIsAuthorized(String) register}
* an access token for an authorization code which encodes the authorization given by a user to this factory's
* client.
*
* @return the account encoding the application that is authorized for a user's account
* @throws RuntimeException in case there was an error while retrieving the token
*/
Account obtainAccessTokenFromAuthorizationCode(String code) throws UnsupportedEncodingException,
ClientProtocolException, IOException, IllegalStateException, ParseException;
String getAccountUrl(Account account);
String getUsersUrl(Account account);
String getResourcesUrl(Permission permission, TimePoint startTime, TimePoint endTime,
Iterable<String> serialNumbers, Iterable<String> streamIds, Account account);
String getResourceDataUrl(TimePoint startTime, TimePoint endTime, Iterable<String> serialNumbers,
Map<Type, Double> typeAndCompression, Account account);
}
@@ -1,7 +0,0 @@
package com.sap.sailing.domain.igtimiadapter;
import com.sap.sailing.domain.igtimiadapter.impl.IgtimiConnectorFactoryImpl;
public interface IgtimiConnectorFactory {
static IgtimiConnectorFactory INSTANCE = new IgtimiConnectorFactoryImpl();
}
@@ -0,0 +1,5 @@
package com.sap.sailing.domain.igtimiadapter;
public enum Permission {
read, modify;
}
@@ -0,0 +1,21 @@
package com.sap.sailing.domain.igtimiadapter;
import com.sap.sailing.domain.common.TimePoint;
public interface Resource {
long getId();
public abstract boolean isBlob();
public abstract Iterable<Permission> getPermissions();
public abstract int[] getDataTypes();
public abstract String getDeviceSerialNumber();
public abstract TimePoint getEndTime();
public abstract TimePoint getStartTime();
}
@@ -0,0 +1,12 @@
package com.sap.sailing.domain.igtimiadapter;
public interface Sensor {
/**
* The ID called "sensor ID" by Igtimi. This identifies the microcontroller-like device that can mange several sensors
* such as wind, GPS or heart rate. Together with the type code and the optional {@link #getDeviceId() device ID} as used
* for heart rate sensors the individual sensor is uniquely defined.
*/
String getTransmitterId();
String getDeviceId();
}
@@ -0,0 +1,11 @@
package com.sap.sailing.domain.igtimiadapter;
public interface User {
long getId();
String getFirstName();
String getSurname();
String getEmail();
}
@@ -0,0 +1,32 @@
package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.igtimiadapter.Sensor;
/**
* Apparent wind angle, relative to the device's or vessel's orientation
*
* @author Axel Uhl (d043530)
*
*/
public class AWA extends Fix {
private final Bearing apparentWindAngle;
public AWA(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
super(sensor, timePoint);
apparentWindAngle = new DegreeBearingImpl(((Number) valuesPerSubindex.get(1)).doubleValue());
}
public Bearing getApparentWindAngle() {
return apparentWindAngle;
}
@Override
protected String localToString() {
return "AWA: "+getApparentWindAngle();
}
}
@@ -0,0 +1,32 @@
package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.igtimiadapter.Sensor;
/**
* Apparent wind speed, relative to the inertial system on which the wind speed was measured
*
* @author Axel Uhl (d043530)
*
*/
public class AWS extends Fix {
private final Speed apparentWindSpeed;
public AWS(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
super(sensor, timePoint);
apparentWindSpeed = new KnotSpeedImpl(((Number) valuesPerSubindex.get(1)).doubleValue());
}
public Speed getApparentWindSpeed() {
return apparentWindSpeed;
}
@Override
protected String localToString() {
return "AWS: "+getApparentWindSpeed();
}
}
@@ -0,0 +1,30 @@
package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.Sensor;
/**
* Cadence value measured by an ANT sensor
*
* @author Axel Uhl (d043530)
*
*/
public class AntCbst extends Fix {
private final int cadence;
public AntCbst(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
super(sensor, timePoint);
cadence = ((Number) valuesPerSubindex.get(1)).intValue();
}
public int getCadence() {
return cadence;
}
@Override
public String localToString() {
return "Cadence "+getCadence();
}
}
@@ -0,0 +1,30 @@
package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.Sensor;
/**
* A heart rate measurement received through a sensor supporting the ANT protocol
*
* @author Axel Uhl (d043530)
*
*/
public class AntHrm extends Fix {
private final int heartRate;
public AntHrm(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
super(sensor, timePoint);
heartRate = ((Number) valuesPerSubindex.get(1)).intValue();
}
public int getHeartRate() {
return heartRate;
}
@Override
protected String localToString() {
return "heart rate "+getHeartRate()+"/min";
}
}
@@ -0,0 +1,24 @@
package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.Sensor;
public class BatteryLevel extends Fix {
private final double percentage;
public BatteryLevel(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
super(sensor, timePoint);
percentage = ((Number) valuesPerSubindex.get(1)).doubleValue();
}
public double getPercentage() {
return percentage;
}
@Override
protected String localToString() {
return "Battery: "+getPercentage()+"%";
}
}
@@ -0,0 +1,32 @@
package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.igtimiadapter.Sensor;
/**
* Course over ground
*
* @author Axel Uhl (d043530)
*
*/
public class COG extends Fix {
private final Bearing courseOverGround;
public COG(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
super(sensor, timePoint);
courseOverGround = new DegreeBearingImpl(((Number) valuesPerSubindex.get(1)).doubleValue());
}
public Bearing getCourseOverGround() {
return courseOverGround;
}
@Override
protected String localToString() {
return "COG: "+getCourseOverGround();
}
}
@@ -0,0 +1,54 @@
package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.Sensor;
public class File extends Fix {
private final TimePoint start;
private final TimePoint end;
private final String fileName;
private final String md5;
private final String contentType;
private final long size;
public File(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
super(sensor, timePoint);
start = timePoint;
end = timePoint; // TODO how are start and end timepoints encoded here?
fileName = ((String) valuesPerSubindex.get(1));
md5 = ((String) valuesPerSubindex.get(2));
contentType = ((String) valuesPerSubindex.get(3));
size = ((Number) valuesPerSubindex.get(4)).longValue();
}
public TimePoint getStart() {
return start;
}
public TimePoint getEnd() {
return end;
}
public String getFileName() {
return fileName;
}
public String getMd5() {
return md5;
}
public String getContentType() {
return contentType;
}
public long getSize() {
return size;
}
@Override
protected String localToString() {
return "File: "+getFileName()+", MD5: "+getMd5()+" content type "+getContentType()+", "+getSize()+" bytes";
}
}
@@ -0,0 +1,30 @@
package com.sap.sailing.domain.igtimiadapter.datatypes;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.Sensor;
public abstract class Fix {
private final TimePoint timePoint;
private final Sensor sensor;
protected Fix(Sensor sensor, TimePoint timePoint) {
super();
this.timePoint = timePoint;
this.sensor = sensor;
}
public TimePoint getTimePoint() {
return timePoint;
}
public Sensor getSensor() {
return sensor;
}
abstract protected String localToString();
@Override
public String toString() {
return localToString() + " at "+getTimePoint()+" from "+getSensor();
}
}
@@ -0,0 +1,26 @@
package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.igtimiadapter.Sensor;
public class GpsAltitude extends Fix {
private final Distance altitude;
public GpsAltitude(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
super(sensor, timePoint);
altitude = new MeterDistance(((Number) valuesPerSubindex.get(1)).doubleValue());
}
public Distance getAltitude() {
return altitude;
}
@Override
protected String localToString() {
return "Altitude "+getAltitude();
}
}
@@ -0,0 +1,26 @@
package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.igtimiadapter.Sensor;
public class GpsLatLong extends Fix {
private final Position position;
public GpsLatLong(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
super(sensor, timePoint);
position = new DegreePosition(((Number) valuesPerSubindex.get(2)).doubleValue(), ((Number) valuesPerSubindex.get(1)).doubleValue());
}
public Position getPosition() {
return position;
}
@Override
protected String localToString() {
return position.toString();
}
}
@@ -0,0 +1,26 @@
package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.igtimiadapter.Sensor;
public class GpsQualityHdop extends Fix {
private final Distance hdop;
public GpsQualityHdop(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
super(sensor, timePoint);
hdop = new MeterDistance(((Number) valuesPerSubindex.get(1)).doubleValue());
}
public Distance getHdop() {
return hdop;
}
@Override
protected String localToString() {
return "HDOP: "+getHdop();
}
}
@@ -0,0 +1,24 @@
package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.Sensor;
public class GpsQualityIndicator extends Fix {
private final int quality;
public GpsQualityIndicator(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
super(sensor, timePoint);
quality = ((Number) valuesPerSubindex.get(1)).intValue();
}
public int getQuality() {
return quality;
}
@Override
protected String localToString() {
return "GPS Quality: "+getQuality();
}
}
@@ -0,0 +1,24 @@
package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.Sensor;
public class GpsQualitySatCount extends Fix {
private final int satCount;
public GpsQualitySatCount(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
super(sensor, timePoint);
satCount = ((Number) valuesPerSubindex.get(1)).intValue();
}
public int getSatCount() {
return satCount;
}
@Override
protected String localToString() {
return ""+getSatCount()+" satellites";
}
}
@@ -0,0 +1,32 @@
package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.igtimiadapter.Sensor;
/**
* True heading; the difference to {@link HDGM} is the local declination at the point in time the measurement was made
*
* @author Axel Uhl (d043530)
*
*/
public class HDG extends Fix {
private final Bearing trueHeading;
public HDG(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
super(sensor, timePoint);
trueHeading = new DegreeBearingImpl(((Number) valuesPerSubindex.get(1)).doubleValue());
}
public Bearing getTrueHeading() {
return trueHeading;
}
@Override
protected String localToString() {
return "HDG: "+getTrueHeading();
}
}
@@ -0,0 +1,32 @@
package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.igtimiadapter.Sensor;
/**
* Magnetic heading
*
* @author Axel Uhl (d043530)
*
*/
public class HDGM extends Fix {
private final Bearing magnetigHeading;
public HDGM(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
super(sensor, timePoint);
magnetigHeading = new DegreeBearingImpl(((Number) valuesPerSubindex.get(1)).doubleValue());
}
public Bearing getMagnetigHeading() {
return magnetigHeading;
}
@Override
protected String localToString() {
return "HDGM: "+getMagnetigHeading();
}
}
@@ -0,0 +1,32 @@
package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.igtimiadapter.Sensor;
/**
* Speed over ground
*
* @author Axel Uhl (d043530)
*
*/
public class SOG extends Fix {
private final Speed speedOverGround;
public SOG(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
super(sensor, timePoint);
speedOverGround = new KnotSpeedImpl(((Number) valuesPerSubindex.get(1)).doubleValue());
}
public Speed getSpeedOverGround() {
return speedOverGround;
}
@Override
protected String localToString() {
return "SOG: "+getSpeedOverGround();
}
}
@@ -0,0 +1,32 @@
package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.igtimiadapter.Sensor;
/**
* Speed through water
*
* @author Axel Uhl (d043530)
*
*/
public class STW extends Fix {
private final Speed speedThroughWater;
public STW(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
super(sensor, timePoint);
speedThroughWater = new KnotSpeedImpl(((Number) valuesPerSubindex.get(1)).doubleValue());
}
public Speed getSpeedThroughWater() {
return speedThroughWater;
}
@Override
protected String localToString() {
return "STW: "+getSpeedThroughWater();
}
}
@@ -0,0 +1,51 @@
package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.HashMap;
import java.util.Map;
public enum Type {
gps_latlong(1, GpsLatLong.class),
gps_quality_indicator(2, GpsQualityIndicator.class),
gps_quality_sat_count(3, GpsQualitySatCount.class),
gps_quality_hdop(4, GpsQualityHdop.class),
gps_altitude(5, GpsAltitude.class),
COG(6, COG.class),
HDGM(7, HDGM.class),
HDG(8, HDG.class),
SOG(9, SOG.class),
STW(10, STW.class),
AWA(11, AWA.class),
AWS(12, AWS.class),
ant_hrm(13, AntHrm.class),
ant_cbst(14, AntCbst.class),
battery_level(15, BatteryLevel.class),
File(16, File.class);
public static Type valueOf(int code) {
return typeByCode.get(code);
}
public int getCode() {
return code;
}
public Class<? extends Fix> getFixClass() {
return fixClass;
}
private final int code;
private final Class<? extends Fix> fixClass;
private final static Map<Integer, Type> typeByCode = new HashMap<>();
private Type(int code, Class<? extends Fix> fixClass) {
this.code = code;
this.fixClass = fixClass;
}
static {
for (Type type : Type.values()) {
typeByCode.put(type.getCode(), type);
}
}
}
@@ -0,0 +1,19 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import com.sap.sailing.domain.igtimiadapter.Account;
import com.sap.sailing.domain.igtimiadapter.User;
public class AccountImpl implements Account {
private final User user;
public AccountImpl(User user) {
super();
this.user = user;
}
@Override
public User getUser() {
return user;
}
}
@@ -0,0 +1,51 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import com.sap.sailing.domain.igtimiadapter.Client;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
/**
* Maintains data about a default {@link Client} that represents this application when interacting with the Igtimi
* server. The corresponding default {@link IgtimiConnectionFactory} can be obtained from within this bundle using
* {@link #getInstance()}.{@link #getConnectionFactory()}. Clients outside this bundle shall track the
* {@link IgtimiConnectionFactory} OSGi service that this activator registeres with the OSGi system upon
* {@link #start(BundleContext)}.
*
* @author Axel Uhl (d043530)
*
*/
public class Activator implements BundleActivator {
private static Activator INSTANCE;
private static final String CLIENT_ID = "d29eae61621af3057db0e638232a027e96b1d2291b1b89a1481dfcac075b0bf4";
private static final String CLIENT_SECRET = "537dbd14a84fcb470c91d85e8c4f8f7a356ac5ffc8727594d1bfe900ee5942ef";
private IgtimiConnectionFactory connectionFactory;
public Activator() {
Client client = new ClientImpl(CLIENT_ID, CLIENT_SECRET, "http://sapsailing.com");
connectionFactory = new IgtimiConnectionFactoryImpl(client);
}
@Override
public void start(BundleContext context) throws Exception {
INSTANCE = this;
context.registerService(IgtimiConnectionFactory.class, connectionFactory, /* properties */ null);
}
public static Activator getInstance() {
if (INSTANCE == null) {
INSTANCE = new Activator(); // probably non-OSGi case, as in test execution
}
return INSTANCE;
}
public IgtimiConnectionFactory getConnectionFactory() {
return connectionFactory;
}
@Override
public void stop(BundleContext context) throws Exception {
}
}
@@ -0,0 +1,31 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import com.sap.sailing.domain.igtimiadapter.Client;
public class ClientImpl implements Client {
private final String id;
private final String secret;
private final String redirectUri;
public ClientImpl(String id, String secret, String redirectUri) {
super();
this.id = id;
this.secret = secret;
this.redirectUri = redirectUri;
}
@Override
public String getId() {
return id;
}
@Override
public String getSecret() {
return secret;
}
@Override
public String getRedirectUri() {
return redirectUri;
}
}
@@ -0,0 +1,66 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
public class ConnectivityUtils {
public static JSONObject getJsonFromResponse(HttpResponse response) throws IllegalStateException, IOException, ParseException {
JSONParser jsonParser = new JSONParser();
final Header contentEncoding = response.getEntity().getContentEncoding();
final Reader reader;
if (contentEncoding == null) {
reader = new InputStreamReader(response.getEntity().getContent());
} else {
reader = new InputStreamReader(response.getEntity().getContent(), contentEncoding.getValue());
}
JSONObject json = (JSONObject) jsonParser.parse(reader);
return json;
}
public static String getContent(HttpResponse response) throws IOException {
StringBuilder result = new StringBuilder();
String line;
BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
while ((line=reader.readLine()) != null) {
result.append(line);
result.append('\n');
}
return result.toString();
}
public static HttpResponse postForm(String baseUrl, final String action, final Map<String, String> inputFieldsToSubmit, DefaultHttpClient client, String referer)
throws UnsupportedEncodingException, IOException, ClientProtocolException {
HttpPost post = new HttpPost(baseUrl+action);
List<NameValuePair> urlParameters = new ArrayList<NameValuePair>();
for (Entry<String, String> nameValue : inputFieldsToSubmit.entrySet()) {
urlParameters.add(new BasicNameValuePair(nameValue.getKey(), nameValue.getValue()));
}
post.setEntity(new UrlEncodedFormEntity(urlParameters));
// TODO check if this is necessary at all
post.setHeader("Origin", "https://www.igtimi.com");
post.setHeader("Referer", referer);
post.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36");
HttpResponse responseForSignIn = client.execute(post);
return responseForSignIn;
}
}
@@ -0,0 +1,65 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.igtimiadapter.Sensor;
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
import com.sap.sailing.domain.igtimiadapter.datatypes.Type;
public class FixFactory {
private static final Logger logger = Logger.getLogger(FixFactory.class.getName());
public Iterable<Fix> createFixes(JSONObject sensorsJson) {
List<Fix> result = new ArrayList<>();
for (Entry<Object, Object> e : sensorsJson.entrySet()) {
String sensorId = (String) e.getKey();
JSONObject typesJson = (JSONObject) e.getValue();
for (Entry<Object, Object> fixTypeAndFixesJson : typesJson.entrySet()) {
final String[] fixTypeAndOptionalColonSeparatedSensorsSubId = ((String) fixTypeAndFixesJson.getKey()).split(":");
int fixType = Integer.valueOf(fixTypeAndOptionalColonSeparatedSensorsSubId[0]);
JSONObject fixesJson = (JSONObject) fixTypeAndFixesJson.getValue();
JSONArray timePointsMillis = (JSONArray) fixesJson.get("t");
int fixIndex = 0;
for (Object timePointMillis : timePointsMillis) {
TimePoint timePoint = new MillisecondsTimePoint((Long) timePointMillis);
Map<Integer, Object> valuesPerSubindex = new HashMap<>();
int i=1;
JSONArray values;
while ((values=(JSONArray) fixesJson.get(""+i)) != null) {
valuesPerSubindex.put(i, (Number) values.get(fixIndex));
i++;
}
Sensor sensor = new SensorImpl(sensorId, fixTypeAndOptionalColonSeparatedSensorsSubId.length<2?null:fixTypeAndOptionalColonSeparatedSensorsSubId[1]);
Fix fix = createFix(sensor, Type.valueOf(fixType), timePoint, valuesPerSubindex);
result.add(fix);
fixIndex++;
}
}
}
return result;
}
private Fix createFix(Sensor sensor, Type fixType, TimePoint timePoint, Map<Integer, Object> valuesPerSubindex) {
try {
Constructor<? extends Fix> constructor = fixType.getFixClass().getConstructor(TimePoint.class, Sensor.class, Map.class);
Fix fix = constructor.newInstance(timePoint, sensor, valuesPerSubindex);
return fix;
} catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
logger.log(Level.SEVERE, "Internal error trying to find fix constructor for fix type "+fixType+" with class "+fixType.getFixClass());
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,338 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.RedirectStrategy;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.impl.client.SystemDefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.Account;
import com.sap.sailing.domain.igtimiadapter.Client;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
import com.sap.sailing.domain.igtimiadapter.Permission;
import com.sap.sailing.domain.igtimiadapter.datatypes.Type;
public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
private static final Logger logger = Logger.getLogger(IgtimiConnectionFactoryImpl.class.getName());
private final Map<Account, String> accessTokensByAccount;
private Map<String, Account> accountsByEmail;
private final Client client;
public IgtimiConnectionFactoryImpl(Client client) {
this.accessTokensByAccount = new HashMap<>();
this.accountsByEmail = new HashMap<>();
this.client = client;
}
@Override
public Account registerAccountForWhichClientIsAuthorized(String accessToken) throws ClientProtocolException, IllegalStateException, IOException, ParseException {
Account account = getAccount(accessToken);
accountsByEmail.put(account.getUser().getEmail(), account);
accessTokensByAccount.put(account, accessToken);
return account;
}
private Account getAccount(String accessToken) throws ClientProtocolException, IOException, IllegalStateException, ParseException {
HttpClient client = new SystemDefaultHttpClient();
HttpGet getAccount = new HttpGet(getApiV1BaseUrl()+"account?access_token="+accessToken);
HttpResponse accountResponse = client.execute(getAccount);
JSONObject accountJson = ConnectivityUtils.getJsonFromResponse(accountResponse);
JSONObject userJson = (JSONObject) accountJson.get("user");
Account account = new AccountImpl(new UserDeserializer().createUserFromJson(userJson));
return account;
}
/**
* @return trailing slash
*/
private String getApiV1BaseUrl() {
return getBaseUrl()+"/api/v1/";
}
private String getSignInUrl() {
return "https://www.igtimi.com/users/sign_in";
}
private String getOauthTokenUrl() {
return getBaseUrl()+"/oauth/token";
}
private String getOauthAuthorizeUrl() throws UnsupportedEncodingException {
return getBaseUrl()+"/oauth/authorize?response_type=code&client_id="+getClient().getId()+"&redirect_uri="+URLEncoder.encode(getClient().getRedirectUri(), "UTF-8");
}
/**
* @return no trailing slash
*/
private String getBaseUrl() {
return "https://www.igtimi.com";
}
@Override
public Account getAccountByEmail(String eMail) {
return accountsByEmail.get(eMail);
}
@Override
public IgtimiConnection connect(Account account) {
return new IgtimiConnectionImpl(this, account);
}
private String getAccessTokenForAccount(Account account) {
return accessTokensByAccount.get(account);
}
@Override
public String getAccountUrl(Account account) {
return getApiV1BaseUrl()+"account?"+getAccessTokenUrlParameter(account);
}
@Override
public String getUsersUrl(Account account) {
return getApiV1BaseUrl()+"users?"+getAccessTokenUrlParameter(account);
}
@Override
public String getResourceDataUrl(TimePoint startTime, TimePoint endTime, Iterable<String> serialNumbers,
Map<Type, Double> typeAndCompression, Account account) {
StringBuilder url = new StringBuilder(getApiV1BaseUrl());
url.append("resources/data?start_time=");
url.append(startTime.asMillis());
url.append("&end_time=");
url.append(endTime.asMillis());
for (String serialNumber : serialNumbers) {
url.append("&serial_numbers[]=");
url.append(serialNumber);
}
for (Entry<Type, Double> e : typeAndCompression.entrySet()) {
url.append("&types["+e.getKey().getCode()+"]="+e.getValue());
}
url.append("&");
url.append(getAccessTokenUrlParameter(account));
return url.toString();
}
@Override
public String getResourcesUrl(Permission permission, TimePoint startTime, TimePoint endTime,
Iterable<String> serialNumbers, Iterable<String> streamIds, Account account) {
StringBuilder url = new StringBuilder(getApiV1BaseUrl());
url.append("resources?");
url.append("permission=");
url.append(permission.name());
if (startTime != null) {
url.append("&start_time=");
url.append(startTime.asMillis());
}
if (endTime != null) {
url.append("&end_time=");
url.append(endTime.asMillis());
}
if (serialNumbers != null) {
for (String serialNumber : serialNumbers) {
url.append("&serial_numbers[]=");
url.append(serialNumber);
}
}
if (streamIds != null) {
for (String streamId : streamIds) {
url.append("&stream_ids[]=");
url.append(streamId);
}
}
url.append("&");
url.append(getAccessTokenUrlParameter(account));
return url.toString();
}
private String getAccessTokenUrlParameter(Account account) {
return "access_token="+getAccessTokenForAccount(account);
}
@Override
public String getAuthorizationUrl() {
return getBaseUrl()+"/oauth";
}
private Client getClient() {
return client;
}
@Override
public Account obtainAccessTokenFromAuthorizationCode(String code) throws ClientProtocolException, IOException, IllegalStateException, ParseException {
HttpClient client = new SystemDefaultHttpClient();
HttpPost post = new HttpPost(getOauthTokenUrl());
List<NameValuePair> urlParameters = new ArrayList<NameValuePair>();
urlParameters.add(new BasicNameValuePair("grant_type", "authorization_code"));
urlParameters.add(new BasicNameValuePair("client_id", getClient().getId()));
urlParameters.add(new BasicNameValuePair("client_secret", getClient().getSecret()));
urlParameters.add(new BasicNameValuePair("code", code));
urlParameters.add(new BasicNameValuePair("redirect_uri", getClient().getRedirectUri()));
post.setEntity(new UrlEncodedFormEntity(urlParameters));
HttpResponse response = client.execute(post);
JSONObject accessTokenJson = ConnectivityUtils.getJsonFromResponse(response);
final Account result;
if (accessTokenJson.get("error") != null) {
throw new RuntimeException(accessTokenJson.toString());
} else {
String accessToken = (String) accessTokenJson.get("access_token");
result = registerAccountForWhichClientIsAuthorized(accessToken);
}
return result;
}
/**
* Tries to authorize our client on behalf of a user identified by e-mail and password.
*
* @return the authorization code which can then be used to obtain a permanent access token to be used by our client
* to access data owned by the user identified by e-mail and password.
*/
public String authorizeAndReturnAuthorizedCode(String userEmail, String userPassword)
throws ClientProtocolException, IOException, IllegalStateException, ParserConfigurationException,
SAXException, ClassNotFoundException, InstantiationException, IllegalAccessException, ClassCastException {
logger.info("Trying to authorize application client " + getClient().getId() + " for user " + userEmail);
DefaultHttpClient client = new SystemDefaultHttpClient();
CookieStore cookieStore = new BasicCookieStore();
client.setCookieStore(cookieStore);
client.setRedirectStrategy(new LaxRedirectStrategy());
HttpGet get = new HttpGet(getOauthAuthorizeUrl());
HttpResponse responseForAuthorize = client.execute(get);
return signInAndReturnAuthorizationForm(client, responseForAuthorize, userEmail, userPassword);
}
private String signInAndReturnAuthorizationForm(DefaultHttpClient client, HttpResponse response,
final String userEmail, final String userPassword) throws ParserConfigurationException, SAXException,
IOException, UnsupportedEncodingException, ClientProtocolException, IllegalStateException,
ClassNotFoundException, InstantiationException, IllegalAccessException, ClassCastException {
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
final String[] action = new String[1];
final Map<String, String> inputFieldsToSubmit = new HashMap<>();
try {
String pageContent = ConnectivityUtils.getContent(response);
String unescapedHtml = StringEscapeUtils.unescapeHtml(pageContent);
parser.parse(new ByteArrayInputStream(unescapedHtml.getBytes("UTF-8")), new DefaultHandler() {
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
if (qName.equals("form")) {
action[0] = attributes.getValue("action");
} else if (qName.equals("input")) {
if (attributes.getValue("value") != null && !attributes.getValue("value").isEmpty()) {
inputFieldsToSubmit.put(attributes.getValue("name"), attributes.getValue("value"));
} else if (attributes.getValue("id") != null) {
if (attributes.getValue("id").contains("email")) {
inputFieldsToSubmit.put(attributes.getValue("name"), userEmail);
} else if (attributes.getValue("id").contains("pass")) {
inputFieldsToSubmit.put(attributes.getValue("name"), userPassword);
}
}
}
super.startElement(uri, localName, qName, attributes);
}
});
} catch (SAXParseException e) {
// swallow; we try to grab what we can; let's hope it was enough...
}
response.getEntity().getContent().close();
final RedirectStrategy oldRedirectStrategy = client.getRedirectStrategy();
final RedirectStrategyExtractingAuthorizationCode codeExtractor = new RedirectStrategyExtractingAuthorizationCode(oldRedirectStrategy);
client.setRedirectStrategy(codeExtractor);
logger.info("Posting sign-in form for user "+userEmail);
HttpResponse authorizationForm = ConnectivityUtils.postForm(getBaseUrl(), action[0], inputFieldsToSubmit, client, getSignInUrl());
if (codeExtractor.getCode() == null) {
logger.info("Client app "+getClient().getId()+" doesn't seem to be authorized yet.");
authorizeAndGetCode(authorizationForm, client);
} else {
logger.info("Client app "+getClient().getId()+" seems to be authorized for user "+userEmail+" already.");
}
return codeExtractor.getCode();
}
/**
* Parses the form in the <code>autorizationForm</code> response and posts it by submitting the form that contains
* the commit button with the value "Authorize". If a redirect strategy is set on the <code>client</code> it will
* see the redirect URL in the <code>Location</code> header. The redirection target is closed immediately.
* @throws ClassCastException
* @throws IllegalAccessException
* @throws InstantiationException
* @throws ClassNotFoundException
*/
private void authorizeAndGetCode(HttpResponse authorizationForm, DefaultHttpClient client)
throws IllegalStateException, SAXException, IOException, ParserConfigurationException,
ClassNotFoundException, InstantiationException, IllegalAccessException, ClassCastException {
// If the user already authorized the app, an empty document will be returned
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
final String action[] = new String[1];
final Map<String, String> inputFieldsToSubmit = new HashMap<>();
try {
String pageContent = ConnectivityUtils.getContent(authorizationForm);
String unescapedHtml = StringEscapeUtils.unescapeHtml(pageContent);
final boolean[] completedAuthorizeForm = new boolean[1];
parser.parse(new ByteArrayInputStream(unescapedHtml.getBytes("UTF-8")), new DefaultHandler() {
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
if (!completedAuthorizeForm[0]) {
if (qName.equals("form")) {
action[0] = attributes.getValue("action");
} else if (qName.equals("input")) {
if (attributes.getValue("value") != null && !attributes.getValue("value").isEmpty()) {
inputFieldsToSubmit.put(attributes.getValue("name"), attributes.getValue("value"));
}
}
}
super.startElement(uri, localName, qName, attributes);
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (qName.equals("form") && inputFieldsToSubmit.get("commit").equals("Authorize")) {
completedAuthorizeForm[0] = true;
}
super.endElement(uri, localName, qName);
}
});
} catch (SAXParseException e) {
// swallow; we try to grab what we can; let's hope it was enough...
logger.warning("The authorization form was not well-formed. Extracted the following parameters so far: "+inputFieldsToSubmit+" ("+e.getMessage()+")");
}
authorizationForm.getEntity().getContent().close();
logger.info("Posting authorization form to authorize client "+getClient().getId()+" for access to data of user");
HttpResponse authorizationResponse = ConnectivityUtils.postForm(getBaseUrl(), action[0], inputFieldsToSubmit, client, /* referer */ getSignInUrl());
authorizationResponse.getEntity().getContent().close();
}
}
@@ -0,0 +1,75 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.SystemDefaultHttpClient;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.Account;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
import com.sap.sailing.domain.igtimiadapter.Permission;
import com.sap.sailing.domain.igtimiadapter.Resource;
import com.sap.sailing.domain.igtimiadapter.User;
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
import com.sap.sailing.domain.igtimiadapter.datatypes.Type;
public class IgtimiConnectionImpl implements IgtimiConnection {
private final Account account;
private final IgtimiConnectionFactory connectionFactory;
public IgtimiConnectionImpl(IgtimiConnectionFactory connectionFactory, Account account) {
this.connectionFactory = connectionFactory;
this.account = account;
}
@Override
public Iterable<User> getUsers() throws IllegalStateException, ClientProtocolException, IOException, ParseException {
HttpClient client = getHttpClient();
HttpGet getUsers = new HttpGet(connectionFactory.getUsersUrl(account));
JSONObject usersJson = ConnectivityUtils.getJsonFromResponse(client.execute(getUsers));
final List<User> result = new ArrayList<>();
for (Object userJson : (JSONArray) usersJson.get("users")) {
User user = new UserDeserializer().createUserFromJson((JSONObject) ((JSONObject) userJson).get("user"));
result.add(user);
}
return result;
}
private HttpClient getHttpClient() {
HttpClient client = new SystemDefaultHttpClient();
return client;
}
@Override
public Iterable<Resource> getResources(Permission permission, TimePoint startTime, TimePoint endTime,
Iterable<String> serialNumbers, Iterable<String> streamIds) throws IllegalStateException, ClientProtocolException, IOException, ParseException {
HttpClient client = getHttpClient();
HttpGet getResources = new HttpGet(connectionFactory.getResourcesUrl(permission, startTime, endTime, serialNumbers, streamIds, account));
JSONObject resourcesJson = ConnectivityUtils.getJsonFromResponse(client.execute(getResources));
final List<Resource> result = new ArrayList<>();
for (Object userJson : (JSONArray) resourcesJson.get("resources")) {
Resource user = new ResourceDeserializer().createResourceFromJson((JSONObject) ((JSONObject) userJson).get("resource"));
result.add(user);
}
return result;
}
@Override
public Iterable<Fix> getResourceData(TimePoint startTime, TimePoint endTime,
Iterable<String> serialNumbers, Map<Type, Double> typeAndCompression) throws IllegalStateException, ClientProtocolException, IOException, ParseException {
HttpClient client = getHttpClient();
HttpGet getResourceData = new HttpGet(connectionFactory.getResourceDataUrl(startTime, endTime, serialNumbers, typeAndCompression, account));
JSONObject resourceDataJson = ConnectivityUtils.getJsonFromResponse(client.execute(getResourceData));
return new FixFactory().createFixes(resourceDataJson);
}
}
@@ -1,7 +0,0 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectorFactory;
public class IgtimiConnectorFactoryImpl implements IgtimiConnectorFactory {
}
@@ -0,0 +1,77 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.http.Header;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.ProtocolException;
import org.apache.http.client.RedirectStrategy;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.protocol.HttpContext;
public class RedirectStrategyExtractingAuthorizationCode implements RedirectStrategy {
private static final Logger logger = Logger.getLogger(RedirectStrategyExtractingAuthorizationCode.class.getName());
private final RedirectStrategy wrappedRedirectStrategy;
private String code;
public RedirectStrategyExtractingAuthorizationCode(RedirectStrategy wrappedRedirectStrategy) {
this.wrappedRedirectStrategy = wrappedRedirectStrategy;
}
public String getCode() {
return code;
}
@Override
public boolean isRedirected(HttpRequest request, HttpResponse response, HttpContext context)
throws ProtocolException {
boolean result = wrappedRedirectStrategy.isRedirected(request, response, context);
int statusCode = response.getStatusLine().getStatusCode();
Header locationHeader = response.getFirstHeader("location");
if (locationHeader != null) {
switch (statusCode) {
case HttpStatus.SC_MOVED_TEMPORARILY:
case HttpStatus.SC_MOVED_PERMANENTLY:
case HttpStatus.SC_TEMPORARY_REDIRECT:
try {
String codeParameter = getCodeFromRedirect(locationHeader);
if (codeParameter != null) {
code = codeParameter;
logger.info("Found authorization code "+code+" in redirect URI "+locationHeader.getValue());
result = false; // don't actually re-direct as the re-direct URI may be a dummy only anyway
}
} catch (URISyntaxException e) {
logger.log(Level.INFO, "Exception while trying to obtain code from redirect URI", e);
}
break;
}
}
return result;
}
@Override
public HttpUriRequest getRedirect(HttpRequest request, HttpResponse response, HttpContext context)
throws ProtocolException {
return wrappedRedirectStrategy.getRedirect(request, response, context);
}
private String getCodeFromRedirect(final Header locationHeader) throws URISyntaxException {
List<NameValuePair> params = URLEncodedUtils.parse(new URI(locationHeader.getValue()), "UTF-8");
for (NameValuePair param : params) {
if (param.getName().equals("code")) {
return param.getValue();
}
}
return null;
}
}
@@ -0,0 +1,44 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.igtimiadapter.Permission;
import com.sap.sailing.domain.igtimiadapter.Resource;
public class ResourceDeserializer {
public Resource createResourceFromJson(JSONObject resourceJson) {
Boolean blob = (Boolean) resourceJson.get("blob");
return new ResourceImpl((Long) resourceJson.get("id"),
new MillisecondsTimePoint(((Double) resourceJson.get("start_time")).longValue()),
new MillisecondsTimePoint(((Double) resourceJson.get("end_time")).longValue()),
(String) resourceJson.get("device_serial_number"),
getDataTypes((JSONArray) resourceJson.get("data_types")),
getPermissions((JSONObject) resourceJson.get("permissions")),
blob == null ? false : blob);
}
private Iterable<Permission> getPermissions(JSONObject permissions) {
final List<Permission> result = new ArrayList<>();
for (Entry<Object, Object> e : permissions.entrySet()) {
if ((Boolean) e.getValue()) {
result.add(Permission.valueOf((String) e.getKey()));
}
}
return result;
}
private int[] getDataTypes(JSONArray jsonArray) {
final int[] result = new int[jsonArray.size()];
int i=0;
for (Object o : jsonArray) {
result[i++] = ((Long) o).intValue();
}
return result;
}
}
@@ -0,0 +1,62 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.Permission;
import com.sap.sailing.domain.igtimiadapter.Resource;
public class ResourceImpl implements Resource {
private final long id;
private final TimePoint startTime;
private final TimePoint endTime;
private final String deviceSerialNumber;
private final int[] dataTypes;
private final Iterable<Permission> permissions;
private final boolean blob;
public ResourceImpl(long id, TimePoint startTime, TimePoint endTime, String deviceSerialNumber, int[] dataTypes,
Iterable<Permission> permissions, boolean blob) {
super();
this.id = id;
this.startTime = startTime;
this.endTime = endTime;
this.deviceSerialNumber = deviceSerialNumber;
this.dataTypes = dataTypes;
this.permissions = permissions;
this.blob = blob;
}
@Override
public long getId() {
return id;
}
@Override
public TimePoint getStartTime() {
return startTime;
}
@Override
public TimePoint getEndTime() {
return endTime;
}
@Override
public String getDeviceSerialNumber() {
return deviceSerialNumber;
}
@Override
public int[] getDataTypes() {
return dataTypes;
}
@Override
public Iterable<Permission> getPermissions() {
return permissions;
}
@Override
public boolean isBlob() {
return blob;
}
}
@@ -0,0 +1,60 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import com.sap.sailing.domain.igtimiadapter.Sensor;
public class SensorImpl implements Sensor {
private final String transmitterId;
private final String deviceId;
protected SensorImpl(String transmitterId, String deviceId) {
super();
this.transmitterId = transmitterId;
this.deviceId = deviceId;
}
@Override
public String getTransmitterId() {
return transmitterId;
}
@Override
public String getDeviceId() {
return deviceId;
}
@Override
public String toString() {
return "Transmitter "+getTransmitterId()+(getDeviceId()==null?"":(", device "+getDeviceId()));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((deviceId == null) ? 0 : deviceId.hashCode());
result = prime * result + ((transmitterId == null) ? 0 : transmitterId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SensorImpl other = (SensorImpl) obj;
if (deviceId == null) {
if (other.deviceId != null)
return false;
} else if (!deviceId.equals(other.deviceId))
return false;
if (transmitterId == null) {
if (other.transmitterId != null)
return false;
} else if (!transmitterId.equals(other.transmitterId))
return false;
return true;
}
}
@@ -0,0 +1,18 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.igtimiadapter.User;
public class UserDeserializer {
public User createUserFromJson(JSONObject userJson) {
Boolean blob = (Boolean) userJson.get("blob");
return new UserImpl((Long) userJson.get("id"),
(String) userJson.get("first_name"),
(String) userJson.get("surname"),
(String) userJson.get("email"),
blob == null ? false : blob);
}
}
@@ -0,0 +1,40 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import com.sap.sailing.domain.igtimiadapter.User;
public class UserImpl implements User {
private final long id;
private final String firstName;
private final String surname;
private final String email;
public UserImpl(long id, String firstName, String surname, String email, boolean blob) {
super();
this.id = id;
this.firstName = firstName;
this.surname = surname;
this.email = email;
}
@Override
public long getId() {
return id;
}
@Override
public String getFirstName() {
return firstName;
}
@Override
public String getSurname() {
return surname;
}
@Override
public String getEmail() {
return email;
}
}
@@ -0,0 +1,62 @@
package com.sap.sailing.domain.igtimiadapter.oauth;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import org.apache.http.client.ClientProtocolException;
import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
import com.sap.sailing.domain.igtimiadapter.impl.Activator;
@Path(AuthorizationCallback.V1)
public class AuthorizationCallback {
private static final Logger logger = Logger.getLogger(AuthorizationCallback.class.getName());
private static final String AUTHORIZATIONCALLBACK = "/authorizationcallback";
static final String V1 = "/v1";
static final String V1_AUTHORIZATIONCALLBACK = V1 + AUTHORIZATIONCALLBACK;
private final IgtimiConnectionFactory connectionFactory;
public AuthorizationCallback() {
connectionFactory = Activator.getInstance().getConnectionFactory();
}
public static String getRedirectUrl() {
// TODO how to find out the external address of this server?
return "http://127.0.0.1:8888/igtimi"+V1_AUTHORIZATIONCALLBACK;
}
@GET
@Produces("application/json;charset=UTF-8")
@Path(AUTHORIZATIONCALLBACK)
public Response obtainAccessToken(@QueryParam("code") String code) throws ClientProtocolException, IOException, IllegalStateException, ParseException, URISyntaxException {
Response result;
try {
connectionFactory.obtainAccessTokenFromAuthorizationCode(code);
result = Response.ok().build();
} catch (Exception e) {
result = Response.status(Status.UNAUTHORIZED).entity(e.getMessage()).build();
logger.log(Level.SEVERE, "Error trying to obtain access token from authentication code "+code, e);
}
return result;
}
@GET
@Produces("application/json;charset=UTF-8")
@Path("uri")
public Response getWithUri(@Context UriInfo info) {
return Response.ok().build();
}
}
@@ -1,17 +0,0 @@
package com.sap.sailing.domain.igtimiadapter.oauth;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
@Path("/v1/authorizationcallback")
public class Callback {
@GET
@Produces("application/json;charset=UTF-8")
@Path("/")
public Response getEvent(@QueryParam("access_token") String accessToken) {
return Response.ok().build();
}
}
@@ -12,6 +12,6 @@ public class RestApiApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
return new HashSet<Class<?>>(Arrays.asList(new Class<?>[] { Callback.class }));
return new HashSet<Class<?>>(Arrays.asList(new Class<?>[] { AuthorizationCallback.class }));
}
}
@@ -31,9 +31,9 @@ import com.sap.sailing.domain.tracking.WindTrack;
*/
public interface DomainObjectFactory {
/**
* @param regatta only needed for backward compatibility because old wind tracks used the regatta name as part of the key
* @param regattaName only needed for backward compatibility because old wind tracks used the regatta name as part of the key
*/
WindTrack loadWindTrack(Regatta regatta, RaceDefinition race, WindSource windSource, long millisecondsOverWhichToAverage);
WindTrack loadWindTrack(String regattaName, RaceDefinition race, WindSource windSource, long millisecondsOverWhichToAverage);
/**
* @return the leaderboard loaded, if successful, or <code>null</code> if the leaderboard couldn't be loaded,
@@ -78,7 +78,7 @@ public interface DomainObjectFactory {
*/
Iterable<Leaderboard> getLeaderboardsNotInGroup(RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry);
Map<? extends WindSource, ? extends WindTrack> loadWindTracks(Regatta regatta, RaceDefinition race,
Map<? extends WindSource, ? extends WindTrack> loadWindTracks(String regattaName, RaceDefinition race,
long millisecondsOverWhichToAverageWind);
Event loadEvent(String name);
@@ -707,9 +707,9 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
}
@Override
public WindTrack loadWindTrack(Regatta regatta, RaceDefinition race, WindSource windSource, long millisecondsOverWhichToAverage) {
public WindTrack loadWindTrack(String regattaName, RaceDefinition race, WindSource windSource, long millisecondsOverWhichToAverage) {
final WindTrack result;
Map<WindSource, WindTrack> resultMap = loadWindTracks(regatta, race, windSource, millisecondsOverWhichToAverage);
Map<WindSource, WindTrack> resultMap = loadWindTracks(regattaName, race, windSource, millisecondsOverWhichToAverage);
if (resultMap.containsKey(windSource)) {
result = resultMap.get(windSource);
} else {
@@ -722,9 +722,9 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
}
@Override
public Map<? extends WindSource, ? extends WindTrack> loadWindTracks(Regatta regatta, RaceDefinition race,
public Map<? extends WindSource, ? extends WindTrack> loadWindTracks(String regattaName, RaceDefinition race,
long millisecondsOverWhichToAverageWind) {
Map<WindSource, WindTrack> result = loadWindTracks(regatta, race, /* constrain wind source */ null, millisecondsOverWhichToAverageWind);
Map<WindSource, WindTrack> result = loadWindTracks(regattaName, race, /* constrain wind source */ null, millisecondsOverWhichToAverageWind);
return result;
}
@@ -733,7 +733,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
* if <code>null</code>, wind for all sources will be loaded; otherwise, only wind data for the wind
* source specified by this argument will be loaded
*/
private Map<WindSource, WindTrack> loadWindTracks(Regatta regatta, RaceDefinition race,
private Map<WindSource, WindTrack> loadWindTracks(String regattaName, RaceDefinition race,
WindSource constrainToWindSource, long millisecondsOverWhichToAverageWind) {
Map<WindSource, WindTrack> result = new HashMap<WindSource, WindTrack>();
try {
@@ -748,7 +748,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
loadWindFix(result, dbWind, millisecondsOverWhichToAverageWind);
}
BasicDBObject queryByName = new BasicDBObject();
queryByName.put(FieldNames.EVENT_NAME.name(), regatta.getName());
queryByName.put(FieldNames.EVENT_NAME.name(), regattaName);
queryByName.put(FieldNames.RACE_NAME.name(), race.getName());
if (constrainToWindSource != null) {
queryByName.put(FieldNames.WIND_SOURCE_NAME.name(), constrainToWindSource.name());
@@ -759,13 +759,13 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
for (DBObject dbWind : windFixesFoundByName) {
Pair<Wind, WindSource> wind = loadWindFix(result, dbWind, millisecondsOverWhichToAverageWind);
// write the wind fix with the new ID-based key and remove the legacy wind fix from the DB
windFixesToMigrate.add(new MongoObjectFactoryImpl(database).storeWindTrackEntry(race, regatta.getName(),
windFixesToMigrate.add(new MongoObjectFactoryImpl(database).storeWindTrackEntry(race, regattaName,
wind.getB(), wind.getA()));
}
logger.info("Migrating "+windFixesFoundByName.size()+" wind fixes of regatta "+regatta.getName()+
logger.info("Migrating "+windFixesFoundByName.size()+" wind fixes of regatta "+regattaName+
" and race "+race.getName()+" to ID-based keys");
windTracks.insert(windFixesToMigrate.toArray(new DBObject[windFixesToMigrate.size()]));
logger.info("Removing "+windFixesFoundByName.size()+" wind fixes that were keyed by the names of regatta "+regatta.getName()+
logger.info("Removing "+windFixesFoundByName.size()+" wind fixes that were keyed by the names of regatta "+regattaName+
" and race "+race.getName());
windTracks.remove(queryByName);
}
@@ -10,7 +10,6 @@ import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.persistence.DomainObjectFactory;
import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.persistence.MongoWindStore;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
@@ -48,26 +47,26 @@ public class MongoWindStoreImpl extends EmptyWindStore implements MongoWindStore
* additions to the wind track will be written to the MongoDB.
*/
@Override
public WindTrack getWindTrack(TrackedRegatta trackedRegatta, TrackedRace trackedRace, WindSource windSource,
public WindTrack getWindTrack(String regattaName, TrackedRace trackedRace, WindSource windSource,
long millisecondsOverWhichToAverage, long delayForWindEstimationCacheInvalidation) {
WindTrack result;
if (windSource.canBeStored()) {
result = domainObjectFactory.loadWindTrack(trackedRegatta.getRegatta(), trackedRace.getRace(), windSource,
result = domainObjectFactory.loadWindTrack(regattaName, trackedRace.getRace(), windSource,
millisecondsOverWhichToAverage);
result.addListener(new MongoWindListener(trackedRace, trackedRegatta.getRegatta().getName(), windSource, mongoObjectFactory, db));
result.addListener(new MongoWindListener(trackedRace, regattaName, windSource, mongoObjectFactory, db));
} else {
result = super.getWindTrack(trackedRegatta, trackedRace, windSource, millisecondsOverWhichToAverage, delayForWindEstimationCacheInvalidation);
result = super.getWindTrack(regattaName, trackedRace, windSource, millisecondsOverWhichToAverage, delayForWindEstimationCacheInvalidation);
}
return result;
}
@Override
public Map<? extends WindSource, ? extends WindTrack> loadWindTracks(TrackedRegatta trackedRegatta,
public Map<? extends WindSource, ? extends WindTrack> loadWindTracks(String regattaName,
TrackedRace trackedRace, long millisecondsOverWhichToAverageWind) {
Map<? extends WindSource, ? extends WindTrack> result = domainObjectFactory.loadWindTracks(
trackedRegatta.getRegatta(), trackedRace.getRace(), millisecondsOverWhichToAverageWind);
regattaName, trackedRace.getRace(), millisecondsOverWhichToAverageWind);
for (Entry<? extends WindSource, ? extends WindTrack> e : result.entrySet()) {
e.getValue().addListener(new MongoWindListener(trackedRace, trackedRegatta.getRegatta().getName(), e.getKey(), mongoObjectFactory, db));
e.getValue().addListener(new MongoWindListener(trackedRace, regattaName, e.getKey(), mongoObjectFactory, db));
}
return result;
}
@@ -14,11 +14,11 @@ import java.util.Arrays;
import java.util.Date;
import java.util.List;
import com.sap.sailing.domain.base.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.swisstimingadapter.Competitor;
@@ -1,5 +1,10 @@
package com.sap.sailing.domain.swisstimingadapter.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -57,11 +62,6 @@ import com.sap.sailing.mongodb.MongoDBService;
import com.sap.sailing.server.RacingEventService;
import com.sap.sailing.server.impl.RacingEventServiceImpl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class EndToEndListeningStoreAndFowardTest {
private static final Logger logger = Logger.getLogger(EndToEndListeningStoreAndFowardTest.class.getName());
@@ -77,7 +77,6 @@ public class EndToEndListeningStoreAndFowardTest {
private SwissTimingAdapterPersistence swissTimingAdapterPersistence;
private SwissTimingFactory swissTimingFactory;
private EmptyWindStore emptyWindStore;
private EmptyRaceLogStore emptyRaceLogStore;
private RacingEventService racingEventService;
private SwissTimingAdapter swissTimingAdapter;
@@ -111,7 +110,7 @@ public class EndToEndListeningStoreAndFowardTest {
sendingSocket = new Socket("localhost", RECEIVE_PORT);
sendingStream = sendingSocket.getOutputStream();
swissTimingFactory = SwissTimingFactory.INSTANCE;
emptyWindStore = EmptyWindStore.INSTANCE;
EmptyWindStore emptyWindStore = EmptyWindStore.INSTANCE;
emptyRaceLogStore = EmptyRaceLogStore.INSTANCE;
transceiver = swissTimingFactory.createSailMasterTransceiver();
DBCollection lastMessageCountCollection = db.getCollection(CollectionNames.LAST_MESSAGE_COUNT.name());
@@ -122,7 +121,7 @@ public class EndToEndListeningStoreAndFowardTest {
final com.sap.sailing.domain.base.impl.DomainFactoryImpl baseDomainFactory = new com.sap.sailing.domain.base.impl.DomainFactoryImpl();
swissTimingAdapter = new SwissTimingAdapterFactoryImpl().getOrCreateSwissTimingAdapter(baseDomainFactory, swissTimingAdapterPersistence);
racingEventService = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(
mongoDBService, baseDomainFactory), PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoDBService), MediaDB.TEST_STUB);
mongoDBService, baseDomainFactory), PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoDBService), MediaDB.TEST_STUB, emptyWindStore);
raceHandles = new ArrayList<RacesHandle>();
}
@@ -371,7 +370,7 @@ public class EndToEndListeningStoreAndFowardTest {
for (String raceToTrack : racesToTrack) {
RacesHandle raceHandle = swissTimingAdapter.addSwissTimingRace(racingEventService,
/* regattaToAddTo */ null /* use a default regatta */, raceToTrack, /* canSendRequests */
"localhost", CLIENT_PORT, false, emptyWindStore, emptyRaceLogStore, -1);
"localhost", CLIENT_PORT, false, emptyRaceLogStore, -1);
raceHandles.add(raceHandle);
if (connector == null) {
connector = swissTimingAdapter.getSwissTimingFactory().getOrCreateSailMasterConnector("localhost",
@@ -16,7 +16,6 @@ import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.WindStore;
import difflib.PatchFailedException;
@@ -56,7 +55,7 @@ public interface DomainFactory {
RaceTrackingConnectivityParameters createTrackingConnectivityParameters(String hostname, int port, String raceID,
boolean canSendRequests, long delayToLiveInMillis,
SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, RaceLogStore raceLogStore, WindStore windStore,
SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, RaceLogStore raceLogStore,
RaceSpecificMessageLoader messageLoader);
RaceType getRaceTypeFromRaceID(String raceID);
@@ -9,7 +9,6 @@ import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.racelog.RaceLogStore;
import com.sap.sailing.domain.tracking.RacesHandle;
import com.sap.sailing.domain.tracking.TrackerManager;
import com.sap.sailing.domain.tracking.WindStore;
public interface SwissTimingAdapter {
List<com.sap.sailing.domain.swisstimingadapter.RaceRecord> getSwissTimingRaceRecords(String hostname, int port,
@@ -25,7 +24,7 @@ public interface SwissTimingAdapter {
* to add the races to. Otherwise, a default regatta as described above will be created and used.
*/
RacesHandle addSwissTimingRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, String raceID, String hostname,
int port, boolean canSendRequests, WindStore windStore, RaceLogStore logStore, long timeoutInMilliseconds)
int port, boolean canSendRequests, RaceLogStore logStore, long timeoutInMilliseconds)
throws InterruptedException, UnknownHostException, IOException, ParseException, Exception;
SwissTimingFactory getSwissTimingFactory();
@@ -4,7 +4,6 @@ import java.io.IOException;
import java.net.UnknownHostException;
import java.text.ParseException;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.racelog.RaceLogStore;
@@ -50,7 +50,6 @@ import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl;
import difflib.PatchFailedException;
@@ -329,10 +328,10 @@ public class DomainFactoryImpl implements DomainFactory {
@Override
public RaceTrackingConnectivityParameters createTrackingConnectivityParameters(String hostname, int port, String raceID,
boolean canSendRequests, long delayToLiveInMillis,
SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, RaceLogStore raceLogStore, WindStore windStore,
SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, RaceLogStore raceLogStore,
RaceSpecificMessageLoader messageLoader) {
return new SwissTimingTrackingConnectivityParameters(hostname, port, raceID, canSendRequests, delayToLiveInMillis,
swissTimingFactory, domainFactory, raceLogStore, windStore, messageLoader);
swissTimingFactory, domainFactory, raceLogStore, messageLoader);
}
}
@@ -25,7 +25,6 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.domain.base.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Speed;
@@ -34,6 +33,7 @@ import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KilometersPerHourSpeedImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
@@ -19,7 +19,6 @@ import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory;
import com.sap.sailing.domain.tracking.RacesHandle;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackerManager;
import com.sap.sailing.domain.tracking.WindStore;
public class SwissTimingAdapterImpl implements SwissTimingAdapter {
private final SwissTimingFactory swissTimingFactory;
@@ -65,11 +64,11 @@ public class SwissTimingAdapterImpl implements SwissTimingAdapter {
@Override
public RacesHandle addSwissTimingRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, String raceID, String hostname,
int port, boolean canSendRequests, WindStore windStore, RaceLogStore logStore, long timeoutInMilliseconds)
int port, boolean canSendRequests, RaceLogStore logStore, long timeoutInMilliseconds)
throws Exception {
return trackerManager.addRace(regattaToAddTo, swissTimingDomainFactory.createTrackingConnectivityParameters(hostname, port,
raceID, canSendRequests, TrackedRace.DEFAULT_LIVE_DELAY_IN_MILLISECONDS, swissTimingFactory,
swissTimingDomainFactory, logStore, windStore, raceSpecificMessageLoader), windStore,
swissTimingDomainFactory, logStore, raceSpecificMessageLoader),
timeoutInMilliseconds);
}
@@ -6,7 +6,6 @@ import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.Util.Triple;
@@ -19,12 +19,11 @@ public class SwissTimingTrackingConnectivityParameters implements RaceTrackingCo
private final SwissTimingFactory swissTimingFactory;
private final DomainFactory domainFactory;
private final RaceLogStore raceLogStore;
private final WindStore windStore;
private final RaceSpecificMessageLoader messageLoader;
private final long delayToLiveInMillis;
public SwissTimingTrackingConnectivityParameters(String hostname, int port, String raceID, boolean canSendRequests, long delayToLiveInMillis,
SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, RaceLogStore raceLogStore, WindStore windStore,
SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, RaceLogStore raceLogStore,
RaceSpecificMessageLoader messageLoader) {
super();
this.hostname = hostname;
@@ -35,18 +34,17 @@ public class SwissTimingTrackingConnectivityParameters implements RaceTrackingCo
this.swissTimingFactory = swissTimingFactory;
this.domainFactory = domainFactory;
this.raceLogStore = raceLogStore;
this.windStore = windStore;
this.messageLoader = messageLoader;
}
@Override
public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry) throws Exception {
public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore) throws Exception {
return swissTimingFactory.createRaceTracker(raceID, hostname, port, canSendRequests, delayToLiveInMillis, raceLogStore, windStore, messageLoader,
domainFactory, trackedRegattaRegistry);
}
@Override
public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry)
public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore)
throws Exception {
return swissTimingFactory.createRaceTracker(regatta, raceID, hostname, port, canSendRequests, delayToLiveInMillis, windStore, messageLoader,
domainFactory, trackedRegattaRegistry);
@@ -8,6 +8,7 @@ import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@@ -18,6 +19,7 @@ import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.Sideline;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.BoatClassImpl;
@@ -25,6 +27,7 @@ import com.sap.sailing.domain.base.impl.CompetitorImpl;
import com.sap.sailing.domain.base.impl.CourseImpl;
import com.sap.sailing.domain.base.impl.MarkImpl;
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.impl.Util;
@@ -33,6 +36,7 @@ import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
import com.sap.sailing.domain.tracking.impl.TrackedRegattaImpl;
import difflib.PatchFailedException;
@@ -245,12 +249,12 @@ public class CourseTest {
Course course = new CourseImpl("Test Course", waypoints);
assertWaypointIndexes(course);
final Set<CompetitorImpl> hasso = Collections.singleton(AbstractLeaderboardTest.createCompetitor("Hasso"));
DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(/* trackedRegatta */ null,
new RaceDefinitionImpl("Test Race", course, new BoatClassImpl("49er", /* upwind start */ true),
hasso), Collections.<Sideline> emptyList(),
EmptyWindStore.INSTANCE, /* delayToLiveInMillis */ 3000,
/* millisecondsOverWhichToAverageWind */ 30000,
/* millisecondsOverWhichToAverageSpeed */ 8000);
DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(/* trackedRegatta */new TrackedRegattaImpl(
new RegattaImpl("test", null, new HashSet<Series>(), false, null, "test", null)),
new RaceDefinitionImpl("Test Race", course, new BoatClassImpl("49er", /* upwind start */true), hasso),
Collections.<Sideline> emptyList(), EmptyWindStore.INSTANCE, /* delayToLiveInMillis */3000,
/* millisecondsOverWhichToAverageWind */30000,
/* millisecondsOverWhichToAverageSpeed */8000);
assertLegStructure(course, trackedRace);
course.removeWaypoint(0);
assertLegStructure(course, trackedRace);
@@ -275,7 +279,8 @@ public class CourseTest {
waypoints.add(wp2);
Course course = new CourseImpl("Test Course", waypoints);
final Set<CompetitorImpl> hasso = Collections.singleton(AbstractLeaderboardTest.createCompetitor("Hasso"));
DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(/* trackedRegatta */ null,
DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(/* trackedRegatta */ new TrackedRegattaImpl(
new RegattaImpl("test", null, new HashSet<Series>(), false, null, "test", null)),
new RaceDefinitionImpl("Test Race", course, new BoatClassImpl("49er", /* upwind start */ true),
hasso), Collections.<Sideline> emptyList(),
EmptyWindStore.INSTANCE, /* delayToLiveInMillis */ 3000,
@@ -0,0 +1,29 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import com.sap.sailing.util.AutoCloseableReadLock;
import com.sap.sailing.util.AutoCloseableWriteLock;
import com.sap.sailing.util.impl.NamedReentrantReadWriteLock;
public class LockAsResourceTest {
@Test
public void testTryWithReadLockAsResource() {
NamedReentrantReadWriteLock lock = new NamedReentrantReadWriteLock("Test Lock", /* fair */ false);
try (AutoCloseableReadLock readlock = new AutoCloseableReadLock(lock)) {
assertEquals(1, lock.getReadHoldCount());
}
assertEquals(0, lock.getReadHoldCount());
}
@Test
public void testTryWithWriteLockAsResource() {
NamedReentrantReadWriteLock lock = new NamedReentrantReadWriteLock("Test Lock", /* fair */ false);
try (AutoCloseableWriteLock readlock = new AutoCloseableWriteLock(lock)) {
assertEquals(1, lock.getWriteHoldCount());
}
assertEquals(0, lock.getWriteHoldCount());
}
}
@@ -26,7 +26,6 @@ import com.sap.sailing.domain.base.Boat;
import com.sap.sailing.domain.base.Timed;
import com.sap.sailing.domain.base.impl.BoatClassImpl;
import com.sap.sailing.domain.base.impl.BoatImpl;
import com.sap.sailing.domain.base.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Position;
@@ -35,6 +34,7 @@ import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.common.impl.MeterPerSecondSpeedWithDegreeBearingImpl;
@@ -1,21 +1,25 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import org.junit.Test;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.Sideline;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.BoatClassImpl;
import com.sap.sailing.domain.base.impl.CourseImpl;
import com.sap.sailing.domain.base.impl.MarkImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
@@ -24,6 +28,7 @@ import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
import com.sap.sailing.domain.tracking.impl.TrackedRegattaImpl;
public class UpdateMarkPassingTest {
@Test
@@ -37,9 +42,10 @@ public class UpdateMarkPassingTest {
when(race.getBoatClass()).thenReturn(new BoatClassImpl("49er", /* typicallyStartsUpwind */ true));
when(race.getCompetitors()).thenReturn(Collections.singleton(competitor));
DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(
/* trackedRegatta */ null, race, Collections.<Sideline> emptyList(), EmptyWindStore.INSTANCE,
/* delayToLiveInMillis */ 1000, /* millisecondsOverWhichToAverageWind */ 30000,
/* millisecondsOverWhichToAverageSpeed */ 30000);
/* trackedRegatta */new TrackedRegattaImpl(new RegattaImpl("test", null, new HashSet<Series>(), false, null,
"test", null)), race, Collections.<Sideline> emptyList(), EmptyWindStore.INSTANCE,
/* delayToLiveInMillis */1000, /* millisecondsOverWhichToAverageWind */30000,
/* millisecondsOverWhichToAverageSpeed */30000);
TimePoint now = MillisecondsTimePoint.now();
TimePoint later = now.plus(1000);
trackedRace.updateMarkPassings(competitor, Arrays.asList(new MarkPassing[] { new MarkPassingImpl(now, waypoint, competitor) }));
@@ -247,7 +247,7 @@ public interface DomainFactory {
Pair<Iterable<com.sap.sailing.domain.base.Competitor>, BoatClass> getCompetitorsAndDominantBoatClass(Race race);
RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI,
TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, boolean simulateWithStartTimeNow, RaceLogStore raceLogStore, WindStore windStore,
TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, boolean simulateWithStartTimeNow, RaceLogStore raceLogStore,
String tracTracUsername, String tracTracPassword);
/**
* Removes all knowledge about <code>tractracRace</code> which includes removing it from the race cache, from the
@@ -47,7 +47,7 @@ public interface TracTracAdapter {
* is stopped; use -1 to wait forever
*/
RacesHandle addTracTracRace(TrackerManager trackerManager, URL paramURL, URI liveURI, URI storedURI,
URI courseDesignUpdateURI, RaceLogStore raceLogStore, WindStore windStore, long timeoutInMilliseconds,
URI courseDesignUpdateURI, RaceLogStore raceLogStore, long timeoutInMilliseconds,
String tracTracUsername, String tracTracPassword) throws MalformedURLException, FileNotFoundException, URISyntaxException, Exception;
/**
@@ -66,7 +66,7 @@ public interface TracTracAdapter {
*/
RacesHandle addTracTracRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, URL paramURL, URI liveURI,
URI storedURI, URI courseDesignUpdateURI, TimePoint trackingStartTime,
TimePoint trackingEndTime, RaceLogStore raceLogStore, WindStore windStore,
TimePoint trackingEndTime, RaceLogStore raceLogStore,
long timeoutForReceivingRaceDefinitionInMilliseconds, boolean simulateWithStartTimeNow, String tracTracUsername, String tracTracPassword)
throws MalformedURLException, FileNotFoundException, URISyntaxException, Exception;
@@ -632,9 +632,9 @@ public class DomainFactoryImpl implements DomainFactory {
@Override
public RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI,
URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
boolean simulateWithStartTimeNow, RaceLogStore raceLogStore, WindStore windStore, String tracTracUsername, String tracTracPassword) {
boolean simulateWithStartTimeNow, RaceLogStore raceLogStore, String tracTracUsername, String tracTracPassword) {
return new RaceTrackingConnectivityParametersImpl(paramURL, liveURI, storedURI, courseDesignUpdateURI, startOfTracking, endOfTracking,
delayToLiveInMillis, simulateWithStartTimeNow, raceLogStore, windStore, this, tracTracUsername, tracTracPassword);
delayToLiveInMillis, simulateWithStartTimeNow, raceLogStore, this, tracTracUsername, tracTracPassword);
}
@Override
@@ -6,7 +6,6 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.Util;
@@ -25,7 +24,6 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
private final TimePoint startOfTracking;
private final TimePoint endOfTracking;
private final RaceLogStore raceLogStore;
private final WindStore windStore;
private final DomainFactory domainFactory;
private final long delayToLiveInMillis;
private final boolean simulateWithStartTimeNow;
@@ -34,7 +32,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
public RaceTrackingConnectivityParametersImpl(URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking,
TimePoint endOfTracking, long delayToLiveInMillis, boolean simulateWithStartTimeNow,
RaceLogStore raceLogStore, WindStore windStore, DomainFactory domainFactory, String tracTracUsername, String tracTracPassword) {
RaceLogStore raceLogStore, DomainFactory domainFactory, String tracTracUsername, String tracTracPassword) {
super();
this.paramURL = paramURL;
this.liveURI = liveURI;
@@ -43,7 +41,6 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
this.startOfTracking = startOfTracking;
this.endOfTracking = endOfTracking;
this.delayToLiveInMillis = delayToLiveInMillis;
this.windStore = windStore;
this.domainFactory = domainFactory;
this.simulateWithStartTimeNow = simulateWithStartTimeNow;
this.raceLogStore = raceLogStore;
@@ -52,7 +49,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
}
@Override
public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry) throws MalformedURLException,
public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore) throws MalformedURLException,
FileNotFoundException, URISyntaxException {
RaceTracker tracker = domainFactory.createRaceTracker(paramURL, liveURI, storedURI, courseDesignUpdateURI, startOfTracking,
endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, raceLogStore, windStore, tracTracUsername, tracTracPassword, trackedRegattaRegistry);
@@ -60,7 +57,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
}
@Override
public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry)
public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore)
throws Exception {
RaceTracker tracker = domainFactory.createRaceTracker(regatta, paramURL, liveURI, storedURI, courseDesignUpdateURI, startOfTracking,
endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, raceLogStore, windStore, tracTracUsername, tracTracPassword, trackedRegattaRegistry);
@@ -8,8 +8,8 @@ import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Logger;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
@@ -68,7 +68,7 @@ public class Simulator {
private void startWindPlayer() {
assert this.trackedRace != null;
for (final Map.Entry<? extends WindSource, ? extends WindTrack> windSourceAndTrack : windStore.loadWindTracks(
trackedRace.getTrackedRegatta(), trackedRace,
trackedRace.getTrackedRegatta().getRegatta().getName(), trackedRace,
/* millisecondsOverWhichToAverageWind doesn't matter because we only use raw fixes */ 10000).entrySet()) {
new Thread("Wind simulator for wind source "+windSourceAndTrack.getKey()+" for tracked race "+trackedRace.getRace().getName()) {
@Override
@@ -47,7 +47,7 @@ public class TracTracAdapterImpl implements TracTracAdapter {
@Override
public RacesHandle addTracTracRace(TrackerManager trackerManager, URL paramURL, URI liveURI, URI storedURI,
URI courseDesignUpdateURI, RaceLogStore raceLogStore, WindStore windStore, long timeoutInMilliseconds,
URI courseDesignUpdateURI, RaceLogStore raceLogStore, long timeoutInMilliseconds,
String tracTracUsername, String tracTracPassword) throws Exception {
return trackerManager.addRace(
/* regattaToAddTo */null,
@@ -55,20 +55,20 @@ public class TracTracAdapterImpl implements TracTracAdapter {
courseDesignUpdateURI,
/* startOfTracking */null,
/* endOfTracking */null, delayToLiveInMillis, /* simulateWithStartTimeNow */false,
raceLogStore, windStore, tracTracUsername, tracTracPassword), windStore, timeoutInMilliseconds);
raceLogStore, tracTracUsername, tracTracPassword), timeoutInMilliseconds);
}
@Override
public RacesHandle addTracTracRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo,
URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking,
TimePoint endOfTracking, RaceLogStore raceLogStore, WindStore windStore,
TimePoint endOfTracking, RaceLogStore raceLogStore,
long timeoutInMilliseconds, boolean simulateWithStartTimeNow, String tracTracUsername, String tracTracPassword) throws Exception {
return trackerManager.addRace(
regattaToAddTo,
getTracTracDomainFactory().createTrackingConnectivityParameters(paramURL, liveURI, storedURI,
courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis,
simulateWithStartTimeNow, raceLogStore, windStore, tracTracUsername, tracTracPassword),
windStore, timeoutInMilliseconds);
simulateWithStartTimeNow, raceLogStore, tracTracUsername, tracTracPassword),
timeoutInMilliseconds);
}
@Override
@@ -88,7 +88,7 @@ public class TracTracAdapterImpl implements TracTracAdapter {
for (RaceRecord rr : jsonService.getRaceRecords()) {
URL paramURL = rr.getParamURL();
regatta = addTracTracRace(trackerManager, paramURL, liveURI, storedURI, courseDesignUpdateURI, raceLogStore,
windStore, timeoutInMilliseconds, tracTracUsername, tracTracPassword).getRegatta();
timeoutInMilliseconds, tracTracUsername, tracTracPassword).getRegatta();
}
return regatta;
}
@@ -251,7 +251,11 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
}
addListenersForStoredDataAndStartController(typeControllers);
// Read event data from configuration file
controlPointPositionPoller = scheduleClientParamsPHPPoller(paramURL, simulator, tracTracUpdateURI, delayToLiveInMillis, tracTracUsername, tracTracPassword);
synchronized (this) {
controlPointPositionPoller = scheduleClientParamsPHPPoller(paramURL, simulator, tracTracUpdateURI,
delayToLiveInMillis, tracTracUsername, tracTracPassword);
notifyAll(); // the stop(boolean) method will try to cancel the controlPointPositionPoller; this may happen even before the above assignment took place; synchronize!
}
}
private URI checkForCachedStoredData(URI storedURI){
@@ -584,12 +588,28 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
stop(/* stop receivers preemtively */ true);
}
@SuppressWarnings("deprecation") // explicitly calling Thread.stop in case IO thread didn't join in three seconds time
private void stop(boolean stopReceiversPreemtively) throws InterruptedException {
synchronized (this) {
while (controlPointPositionPoller == null) {
wait(); // constructor will notify all waiters once the controlPointPositionPoller is set
}
}
controlPointPositionPoller.cancel(/* mayInterruptIfRunning */ false);
new Thread("TracTrac Controller Stopper for "+getID()) {
public void run() {
controller.stop(/* abortStored */ true);
try {
ioThread.join();
if (ioThread.isAlive()) {
logger.severe("Tractrac IO thread in tracker "+getID()+" for race(s) "+getRaces()+" joined but is still active. Very strange.");
} else {
logger.info("Joined TracTrac IO thread in tracker "+getID()+" for race(s) "+getRaces());
}
lastStatus = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.FINISHED, /* will be ignored */ 1.0);
updateStatusOfTrackedRaces();
} catch (InterruptedException e) {
logger.log(Level.INFO, "Interrupted while trying to join TracTrac DataController thread for "+getID());
} // wait no more than three seconds
}
}.start();
for (Receiver receiver : receivers) {
@@ -599,15 +619,6 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
receiver.stopAfterProcessingQueuedEvents();
}
}
ioThread.join(3000); // wait no more than three seconds
if (ioThread.isAlive()) {
ioThread.stop();
logger.warning("Tractrac IO thread in tracker "+getID()+" for race(s) "+getRaces()+" didn't join in 3s. Stopped forcefully.");
} else {
logger.info("Joined TracTrac IO thread in tracker "+getID()+" for race(s) "+getRaces());
}
lastStatus = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.FINISHED, /* will be ignored */ 1.0);
updateStatusOfTrackedRaces();
}
protected DataController getController() {
@@ -686,7 +697,9 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
@Override
public void storedDataProgress(float progress) {
logger.info("Stored data progress in tracker "+getID()+" for race(s) "+getRaces()+": "+progress);
if (lastStatus.getStatus().equals(TrackedRaceStatusEnum.ERROR)) {
return;
}
Integer counter = 0;
final Pair<Integer, Float> lastProgressPair = lastProgressPerID.get(getID());
if (lastProgressPair != null) {
@@ -700,6 +713,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
/* make sure to indicate that this race is erroneous */
lastStatus = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.ERROR, 0.0);
updateStatusOfTrackedRaces();
return;
} catch (InterruptedException e) {
e.printStackTrace();
}
@@ -708,6 +722,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
}
}
}
logger.info("Stored data progress in tracker "+getID()+" for race(s) "+getRaces()+": "+progress);
lastStatus = new TrackedRaceStatusImpl(progress==1.0 ? TrackedRaceStatusEnum.TRACKING : TrackedRaceStatusEnum.LOADING, progress);
lastProgressPerID.put(getID(), new Pair<Integer, Float>(counter, progress));
updateStatusOfTrackedRaces();
@@ -31,7 +31,6 @@ import com.sap.sailing.util.impl.RaceColumnListeners;
public interface RaceColumn extends Named {
/**
* Sets the information object used to access the race column's race logs.
* @param information
*/
void setRaceLogInformation(RaceLogInformation information);
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.base.impl;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.RadianBearingImpl;
import com.sap.sailing.domain.common.impl.Util.Triple;
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.base.impl;
import com.sap.sailing.domain.base.SpeedWithConfidence;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.confidence.ScalableValue;
public class SpeedWithConfidenceImpl<RelativeTo> extends HasConfidenceImpl<Double, Speed, RelativeTo> implements SpeedWithConfidence<RelativeTo> {
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.masterdataimport;
import java.util.Map;
import java.util.Set;
import com.sap.sailing.domain.common.RaceIdentifier;
@@ -9,14 +10,16 @@ public class RaceColumnMasterData {
private final boolean medal;
private final Map<String, RaceIdentifier> raceIdentifiersByFleetName;
private Double factor;
private Set<WindTrackMasterData> windTracks;
public RaceColumnMasterData(String name, boolean medal, Map<String, RaceIdentifier> raceIdentifiersByFleetName,
Double factor) {
Double factor, Set<WindTrackMasterData> windTracks) {
super();
this.name = name;
this.medal = medal;
this.raceIdentifiersByFleetName = raceIdentifiersByFleetName;
this.factor = factor;
this.windTracks = windTracks;
}
public String getName() {
@@ -34,5 +37,9 @@ public class RaceColumnMasterData {
public Double getFactor() {
return factor;
}
public Set<WindTrackMasterData> getWindTrackMasterData() {
return windTracks;
}
}
@@ -0,0 +1,66 @@
package com.sap.sailing.domain.masterdataimport;
import java.io.Serializable;
import java.util.Set;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID;
import com.sap.sailing.domain.tracking.Wind;
public class WindTrackMasterData {
private final WindSourceType windSourceType;
private final Serializable windSourceId;
private final Set<Wind> fixes;
private final String regattaName;
private final String raceName;
private final Serializable raceId;
public WindTrackMasterData(String windSourceTypeName, Serializable windSourceId, Set<Wind> fixes,
String regattaName, String raceName, Serializable raceId) {
this.windSourceId = windSourceId;
this.fixes = fixes;
this.regattaName = regattaName;
this.raceName = raceName;
this.raceId = raceId;
this.windSourceType = WindSourceType.valueOf(windSourceTypeName);
}
public WindSourceType getWindSourceType() {
return windSourceType;
}
public Serializable getWindSourceId() {
return windSourceId;
}
public Set<Wind> getFixes() {
return fixes;
}
public String getRegattaName() {
return regattaName;
}
public String getRaceName() {
return raceName;
}
public Serializable getRaceId() {
return raceId;
}
public WindSource getWindSource() {
WindSource source;
if (windSourceId != null) {
source = new WindSourceWithAdditionalID(windSourceType, windSourceId.toString());
} else {
source = new WindSourceImpl(windSourceType);
}
return source;
}
}
@@ -107,26 +107,29 @@ public class PerRaceAndCompetitorPolarSheetGenerationWorker implements Runnable
} else {
GPSFixTrack<Competitor, GPSFixMoving> track = race.getTrack(competitor);
track.lockForRead();
Iterator<GPSFixMoving> fixesIterator = track.getFixesIterator(startTime, true);
TimePoint lastConsideredTimePoint = null;
if (finishedEarlyAtWaypoint != -1) {
NavigableSet<MarkPassing> markPassings = race.getMarkPassings(competitor);
MarkPassing lastConsideredPassing = markPassings.last();
lastConsideredPassing = markPassings.lower(lastConsideredPassing);
lastConsideredTimePoint = lastConsideredPassing.getTimePoint();
}
boolean reachedEnd = false;
while (fixesIterator.hasNext() && reachedEnd == false) {
GPSFixMoving fix = fixesIterator.next();
if (fix.getTimePoint().after(endTime)
|| (lastConsideredTimePoint != null && fix.getTimePoint().after(lastConsideredTimePoint))) {
reachedEnd = true;
} else {
addFixIfValid(track, fix);
try {
Iterator<GPSFixMoving> fixesIterator = track.getFixesIterator(startTime, true);
TimePoint lastConsideredTimePoint = null;
if (finishedEarlyAtWaypoint != -1) {
NavigableSet<MarkPassing> markPassings = race.getMarkPassings(competitor);
MarkPassing lastConsideredPassing = markPassings.last();
lastConsideredPassing = markPassings.lower(lastConsideredPassing);
lastConsideredTimePoint = lastConsideredPassing.getTimePoint();
}
boolean reachedEnd = false;
while (fixesIterator.hasNext() && reachedEnd == false) {
GPSFixMoving fix = fixesIterator.next();
if (fix.getTimePoint().after(endTime)
|| (lastConsideredTimePoint != null && fix.getTimePoint().after(lastConsideredTimePoint))) {
reachedEnd = true;
} else {
addFixIfValid(track, fix);
}
}
} finally {
track.unlockAfterRead();
done = true;
}
track.unlockAfterRead();
done = true;
}
}
@@ -37,16 +37,20 @@ public class PolarFix {
this.windSpeed = windSpeed;
Set<WindSource> windSourcesToExclude;
if (settings.useOnlyEstimatedForWindDirection()) {
windSourcesToExclude = collectWindSourcesToIgnoreForBearing(race, true);
windSourcesToExclude = collectWindSourcesToIgnoreForBearing(race, /* exclude course based */ true);
} else {
windSourcesToExclude = new HashSet<WindSource>();
}
Wind windEstimated = race.getWind(position, fix.getTimePoint(), windSourcesToExclude);
if (windEstimated == null) {
windSourcesToExclude = collectWindSourcesToIgnoreForBearing(race, false);
// no estimated wind; try to include course layout
windSourcesToExclude = collectWindSourcesToIgnoreForBearing(race, /* exclude course based */ false);
windEstimated = race.getWind(position, fix.getTimePoint(), windSourcesToExclude);
}
if (windEstimated == null) {
windEstimated = windSpeed; // maybe no upwind start; need to default to measured wind speed/direction
}
Bearing windBearing = windEstimated.getFrom();
angleToWind = bearing.getDifferenceTo(windBearing).getDegrees();
}
@@ -20,7 +20,7 @@ public interface RaceTrackingConnectivityParameters {
/**
* Starts a {@link RaceTracker} using the connectivity parameters provided by this object.
*/
RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry) throws Exception;
RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore) throws Exception;
/**
* Starts a {@link RaceTracker}, associating the resulting races with the {@link Regatta} passed as argument
@@ -28,7 +28,7 @@ public interface RaceTrackingConnectivityParameters {
* parameters. This is particularly useful if a predefined regatta with {@link Series} and {@link Fleet}s
* is to be used.
*/
RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry) throws Exception;
RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore) throws Exception;
/**
* Deliver an ID object equal to that of the {@link RaceTracker#getID()} delivered by the {@link RaceTracker}
@@ -0,0 +1,150 @@
package com.sap.sailing.domain.tracking;
import java.io.ObjectOutputStream;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.tracking.impl.CombinedWindTrackImpl;
import com.sap.sailing.util.impl.LockUtil;
import com.sap.sailing.util.impl.NamedReentrantReadWriteLock;
public abstract class TrackedRaceWithWindEssentials implements TrackedRace {
private static final long serialVersionUID = -3937032323498604671L;
private static final Logger logger = Logger.getLogger(TrackedRaceWithWindEssentials.class.getName());
/**
* Serializing an instance of this class has to serialized the various data structures holding the tracked race's
* state. When a race is currently on, these structures change very frequently, and
* {@link ConcurrentModificationException}s during serialization will be the norm rather than the exception. To
* avoid this, all modifications to any data structure that is not in itself synchronized obtains this lock's
* <em>read</em> lock (note that this may be confusing at first, but we'd like to support many concurrent writers;
* they each perform their own locking on the individual data structures they write; we only want to lock out a
* single serialization call which with this lock is represented as the "writer"). The serialization method
* {@link #writeObject(ObjectOutputStream)} obtains the <em>write</em> lock. Deadlocks are avoided because the
* serialization, once it obtains this write lock, it keeps serializing and releases the write lock when it's done,
* without doing any further synchronization or locking.
*/
private final NamedReentrantReadWriteLock serializationLock;
private transient CombinedWindTrackImpl combinedWindTrack;
/**
* A tracked race can maintain a number of sources for wind information from which a client can select. As all
* intra-leg computations are done dynamically based on wind information, selecting a different wind information
* source can alter the intra-leg results. See {@link #currentWindSource}.
*/
protected final Map<WindSource, WindTrack> windTracks;
protected final RaceDefinition race;
protected transient WindStore windStore;
protected final TrackedRegatta trackedRegatta;
protected long millisecondsOverWhichToAverageWind;
public TrackedRaceWithWindEssentials(final RaceDefinition race, final TrackedRegatta trackedRegatta, final WindStore windStore,
final long millisecondsOverWhichToAverageWind) {
this.race = race;
this.millisecondsOverWhichToAverageWind = millisecondsOverWhichToAverageWind;
windTracks = new ConcurrentHashMap<WindSource, WindTrack>();
this.windStore = windStore;
this.trackedRegatta = trackedRegatta;
this.serializationLock = new NamedReentrantReadWriteLock("Serialization lock for tracked race "
+ race.getName(), /* fair */ true);
}
/**
* For wind sources of the special type {@link WindSourceType#COMBINED}, emits a new {@link CombinedWindTrackImpl}
* which will not be added to {@link #windTracks} and will not lead to the wind source being listed in
* {@link #getWindSources()} or {@link #getWindSources(WindSourceType)}. For all other wind sources, checks
* {@link #windTracks} for the respective source. If found, it's returned; otherwise the wind track is created
* through the {@link #windStore} using {@link #createWindTrack(WindSource, long)} and added to {@link #windTracks}
* before being returned.
*
* @param delayForWindEstimationCacheInvalidation
* if <code>-1</code> and the parameter is accessed, it will be replaced by
* {@link #getMillisecondsOverWhichToAverageWind()}/2
*/
@Override
public WindTrack getOrCreateWindTrack(WindSource windSource, long delayForWindEstimationCacheInvalidation) {
WindTrack result;
if (windSource.getType() == WindSourceType.COMBINED) {
if (combinedWindTrack == null) {
combinedWindTrack = new CombinedWindTrackImpl(this, WindSourceType.COMBINED.getBaseConfidence());
}
result = combinedWindTrack;
} else {
synchronized (windTracks) {
result = windTracks.get(windSource);
if (result == null) {
result = createWindTrack(windSource,
delayForWindEstimationCacheInvalidation == -1 ? getMillisecondsOverWhichToAverageWind() / 2
: delayForWindEstimationCacheInvalidation);
LockUtil.lockForRead(getSerializationLock());
try {
windTracks.put(windSource, result);
} finally {
LockUtil.unlockAfterRead(getSerializationLock());
}
}
}
}
return result;
}
@Override
public WindTrack getOrCreateWindTrack(WindSource windSource) {
return getOrCreateWindTrack(windSource, -1);
}
/**
* Creates a wind track for the <code>windSource</code> specified and stores it in {@link #windTracks}. The
* averaging interval is set according to the averaging interval set for all other wind sources, or the default if
* no other wind source exists yet.
*/
protected WindTrack createWindTrack(WindSource windSource, long delayForWindEstimationCacheInvalidation) {
WindTrack result = windStore.getWindTrack(trackedRegatta.getRegatta().getName(), this, windSource, millisecondsOverWhichToAverageWind,
delayForWindEstimationCacheInvalidation);
return result;
}
@Override
public Iterable<WindSource> getWindSources(WindSourceType type) {
Set<WindSource> result = new HashSet<WindSource>();
for (WindSource windSource : getWindSources()) {
if (windSource.getType() == type) {
result.add(windSource);
}
}
return result;
}
@Override
public Iterable<WindSource> getWindSources() {
while (true) {
try {
return new HashSet<WindSource>(windTracks.keySet());
} catch (ConcurrentModificationException cme) {
logger.info("Caught " + cme + "; trying again.");
}
}
}
protected NamedReentrantReadWriteLock getSerializationLock() {
return serializationLock;
}
}
@@ -4,8 +4,9 @@ import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
public interface TrackerManager {
@@ -18,10 +19,8 @@ public interface TrackerManager {
* identifier; if the regatta is found, it is used to add the races to, and
* {@link #setRegattaForRace(Regatta, RaceDefinition)} is called to remember the association
* persistently. Otherwise, a default regatta as described above will be created and used.
* @param windStore
* must not be <code>null</code>, but can, e.g., be an {@link EmptyWindStore}
*/
RacesHandle addRace(RegattaIdentifier regattaToAddTo, RaceTrackingConnectivityParameters params, WindStore windStore, long timeoutInMilliseconds)
RacesHandle addRace(RegattaIdentifier regattaToAddTo, RaceTrackingConnectivityParameters params, long timeoutInMilliseconds)
throws MalformedURLException, FileNotFoundException, URISyntaxException, Exception;
}
@@ -14,7 +14,7 @@ import com.sap.sailing.domain.common.WindSource;
*
*/
public interface WindStore {
WindTrack getWindTrack(TrackedRegatta trackedRegatta, TrackedRace trackedRace, WindSource windSource,
WindTrack getWindTrack(String regattaName, TrackedRace trackedRace, WindSource windSource,
long millisecondsOverWhichToAverage, long delayForWindEstimationCacheInvalidation);
/**
@@ -22,6 +22,6 @@ public interface WindStore {
*
* @return a map that is never <code>null</code> but may be empty
*/
Map<? extends WindSource, ? extends WindTrack> loadWindTracks(TrackedRegatta trackedRegatta,
Map<? extends WindSource, ? extends WindTrack> loadWindTracks(String regattaName,
TrackedRace trackedRace, long millisecondsOverWhichToAverageWind);
}
@@ -5,7 +5,6 @@ import java.util.Map;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTrack;
@@ -14,7 +13,7 @@ public class EmptyWindStore implements WindStore {
public static EmptyWindStore INSTANCE = new EmptyWindStore();
@Override
public WindTrack getWindTrack(TrackedRegatta trackedRegatta, TrackedRace trackedRace, WindSource windSource,
public WindTrack getWindTrack(String regattaName, TrackedRace trackedRace, WindSource windSource,
long millisecondsOverWhichToAverage, long delayForWindEstimationCacheInvalidation) {
switch (windSource.getType()) {
case COURSE_BASED:
@@ -28,7 +27,7 @@ public class EmptyWindStore implements WindStore {
}
@Override
public Map<? extends WindSource, ? extends WindTrack> loadWindTracks(TrackedRegatta trackedRegatta,
public Map<? extends WindSource, ? extends WindTrack> loadWindTracks(String regattaName,
TrackedRace trackedRace, long millisecondsOverWhichToAverageWind) {
return Collections.emptyMap();
}
@@ -20,7 +20,6 @@ import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
import com.sap.sailing.domain.base.SpeedWithConfidence;
import com.sap.sailing.domain.base.Timed;
import com.sap.sailing.domain.base.impl.BearingWithConfidenceImpl;
import com.sap.sailing.domain.base.impl.KnotSpeedImpl;
import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl;
import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl;
import com.sap.sailing.domain.common.Bearing;
@@ -29,6 +28,7 @@ import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.impl.NauticalMileDistance;
@@ -9,7 +9,6 @@ import java.util.NavigableSet;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.ManeuverType;
@@ -18,6 +17,7 @@ import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.common.impl.Util.Pair;
@@ -86,8 +86,8 @@ import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
import com.sap.sailing.domain.tracking.TrackedRaceWithWindEssentials;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindStore;
@@ -100,7 +100,7 @@ import com.sap.sailing.util.impl.ArrayListNavigableSet;
import com.sap.sailing.util.impl.LockUtil;
import com.sap.sailing.util.impl.NamedReentrantReadWriteLock;
public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials implements CourseListener {
private static final long serialVersionUID = -4825546964220003507L;
private static final Logger logger = Logger.getLogger(TrackedRaceImpl.class.getName());
@@ -110,10 +110,6 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
// TODO make this variable
private static final long DELAY_FOR_CACHE_CLEARING_IN_MILLISECONDS = 7500;
private final RaceDefinition race;
private final TrackedRegatta trackedRegatta;
private TrackedRaceStatus status;
private final Object statusNotifier;
@@ -203,13 +199,6 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
*/
private transient SmartFutureCache<Competitor, Triple<TimePoint, TimePoint, List<Maneuver>>, EmptyUpdateInterval> maneuverCache;
/**
* A tracked race can maintain a number of sources for wind information from which a client can select. As all
* intra-leg computations are done dynamically based on wind information, selecting a different wind information
* source can alter the intra-leg results. See {@link #currentWindSource}.
*/
private final Map<WindSource, WindTrack> windTracks;
private transient Map<TimePoint, Future<Wind>> directionFromStartToNextMarkCache;
private final ConcurrentHashMap<Mark, GPSFixTrack<Mark, GPSFix>> markTracks;
@@ -220,15 +209,9 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
private final Map<Mark, StartToNextMarkCacheInvalidationListener> startToNextMarkCacheInvalidationListeners;
protected long millisecondsOverWhichToAverageWind;
private transient WindStore windStore;
private transient Timer cacheInvalidationTimer;
private transient Object cacheInvalidationTimerLock;
private transient CombinedWindTrackImpl combinedWindTrack;
protected transient HashMap<Serializable, RaceLog> attachedRaceLogs;
/**
@@ -244,20 +227,6 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
private WindLoadingState windLoadingCompleted;
private transient CrossTrackErrorCache crossTrackErrorCache;
/**
* Serializing an instance of this class has to serialized the various data structures holding the tracked race's
* state. When a race is currently on, these structures change very frequently, and
* {@link ConcurrentModificationException}s during serialization will be the norm rather than the exception. To
* avoid this, all modifications to any data structure that is not in itself synchronized obtains this lock's
* <em>read</em> lock (note that this may be confusing at first, but we'd like to support many concurrent writers;
* they each perform their own locking on the individual data structures they write; we only want to lock out a
* single serialization call which with this lock is represented as the "writer"). The serialization method
* {@link #writeObject(ObjectOutputStream)} obtains the <em>write</em> lock. Deadlocks are avoided because the
* serialization, once it obtains this write lock, it keeps serializing and releases the write lock when it's done,
* without doing any further synchronization or locking.
*/
private final NamedReentrantReadWriteLock serializationLock;
/**
* Wind loading is started in a background thread during object construction. If a client needs to ensure that wind loading
@@ -271,23 +240,18 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
public TrackedRaceImpl(final TrackedRegatta trackedRegatta, RaceDefinition race, final Iterable<Sideline> sidelines, final WindStore windStore,
long delayToLiveInMillis, final long millisecondsOverWhichToAverageWind,
long millisecondsOverWhichToAverageSpeed, long delayForWindEstimationCacheInvalidation) {
super();
super(race, trackedRegatta, windStore, millisecondsOverWhichToAverageWind);
locksForMarkPassings = new IdentityHashMap<>();
attachedRaceLogs = new HashMap<>();
this.status = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.PREPARED, 0.0);
this.statusNotifier = new Object[0];
this.serializationLock = new NamedReentrantReadWriteLock("Serialization lock for tracked race "
+ race.getName(), /* fair */ true);
this.windLoadingLock = new NamedReentrantReadWriteLock("Wind loading lock for tracked race "
+ race.getName(), /* fair */ false);
this.cacheInvalidationTimerLock = new Object();
this.updateCount = 0;
this.race = race;
this.windStore = windStore;
this.windSourcesToExclude = new HashSet<WindSource>();
this.directionFromStartToNextMarkCache = new HashMap<TimePoint, Future<Wind>>();
this.millisecondsOverWhichToAverageSpeed = millisecondsOverWhichToAverageSpeed;
this.millisecondsOverWhichToAverageWind = millisecondsOverWhichToAverageWind;
this.delayToLiveInMillis = delayToLiveInMillis;
this.startToNextMarkCacheInvalidationListeners = new ConcurrentHashMap<Mark, TrackedRaceImpl.StartToNextMarkCacheInvalidationListener>();
this.maneuverCache = createManeuverCache();
@@ -337,7 +301,6 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
MarkPassingByTimeComparator.INSTANCE));
}
markPassingsTimes = new ArrayList<Pair<Waypoint, Pair<TimePoint, TimePoint>>>();
windTracks = new ConcurrentHashMap<WindSource, WindTrack>();
windLoadingCompleted = WindLoadingState.NOT_STARTED;
// When this tracked race is to be serialized, wait for the loading of the wind tracks to complete.
new Thread("Wind loader for tracked race " + getRace().getName()) {
@@ -351,7 +314,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
}
try {
final Map<? extends WindSource, ? extends WindTrack> loadedWindTracks = windStore.loadWindTracks(
trackedRegatta, TrackedRaceImpl.this, millisecondsOverWhichToAverageWind);
trackedRegatta.getRegatta().getName(), TrackedRaceImpl.this, millisecondsOverWhichToAverageWind);
windTracks.putAll(loadedWindTracks);
updateEventTimePoints(loadedWindTracks);
} finally {
@@ -373,7 +336,6 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
WindSource trackBasedWindSource = new WindSourceImpl(WindSourceType.TRACK_BASED_ESTIMATION);
windTracks.put(trackBasedWindSource,
getOrCreateWindTrack(trackBasedWindSource, delayForWindEstimationCacheInvalidation));
this.trackedRegatta = trackedRegatta;
competitorRankings = new HashMap<TimePoint, List<Competitor>>();
competitorRankingsLocks = new HashMap<TimePoint, NamedReentrantReadWriteLock>();
// now wait until wind loading has at least started; then we know that the serialization lock is safely held by the loader
@@ -1152,61 +1114,6 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
return result;
}
/**
* For wind sources of the special type {@link WindSourceType#COMBINED}, emits a new {@link CombinedWindTrackImpl}
* which will not be added to {@link #windTracks} and will not lead to the wind source being listed in
* {@link #getWindSources()} or {@link #getWindSources(WindSourceType)}. For all other wind sources, checks
* {@link #windTracks} for the respective source. If found, it's returned; otherwise the wind track is created
* through the {@link #windStore} using {@link #createWindTrack(WindSource, long)} and added to {@link #windTracks}
* before being returned.
*
* @param delayForWindEstimationCacheInvalidation
* if <code>-1</code> and the parameter is accessed, it will be replaced by
* {@link #getMillisecondsOverWhichToAverageWind()}/2
*/
@Override
public WindTrack getOrCreateWindTrack(WindSource windSource, long delayForWindEstimationCacheInvalidation) {
WindTrack result;
if (windSource.getType() == WindSourceType.COMBINED) {
if (combinedWindTrack == null) {
combinedWindTrack = new CombinedWindTrackImpl(this, WindSourceType.COMBINED.getBaseConfidence());
}
result = combinedWindTrack;
} else {
synchronized (windTracks) {
result = windTracks.get(windSource);
if (result == null) {
result = createWindTrack(windSource,
delayForWindEstimationCacheInvalidation == -1 ? getMillisecondsOverWhichToAverageWind() / 2
: delayForWindEstimationCacheInvalidation);
LockUtil.lockForRead(getSerializationLock());
try {
windTracks.put(windSource, result);
} finally {
LockUtil.unlockAfterRead(getSerializationLock());
}
}
}
}
return result;
}
@Override
public WindTrack getOrCreateWindTrack(WindSource windSource) {
return getOrCreateWindTrack(windSource, -1);
}
/**
* Creates a wind track for the <code>windSource</code> specified and stores it in {@link #windTracks}. The
* averaging interval is set according to the averaging interval set for all other wind sources, or the default if
* no other wind source exists yet.
*/
protected WindTrack createWindTrack(WindSource windSource, long delayForWindEstimationCacheInvalidation) {
WindTrack result = windStore.getWindTrack(trackedRegatta, this, windSource, millisecondsOverWhichToAverageWind,
delayForWindEstimationCacheInvalidation);
return result;
}
@Override
public boolean hasWindData() {
boolean result = false;
@@ -2338,28 +2245,6 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
return trackedLeg == null ? null : trackedLeg.getWindwardDistanceToOverallLeader(timePoint);
}
@Override
public Iterable<WindSource> getWindSources(WindSourceType type) {
Set<WindSource> result = new HashSet<WindSource>();
for (WindSource windSource : getWindSources()) {
if (windSource.getType() == type) {
result.add(windSource);
}
}
return result;
}
@Override
public Iterable<WindSource> getWindSources() {
while (true) {
try {
return new HashSet<WindSource>(windTracks.keySet());
} catch (ConcurrentModificationException cme) {
logger.info("Caught " + cme + "; trying again.");
}
}
}
@Override
public Iterable<Mark> getMarks() {
while (true) {
@@ -2600,10 +2485,6 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
return starboardMarkPosition;
}
protected NamedReentrantReadWriteLock getSerializationLock() {
return serializationLock;
}
protected NamedReentrantReadWriteLock getWindLoadingLock() {
return windLoadingLock;
}
Loaded 100 of 189 files, more files were not shown because too many files have changed in this diff. Show more