mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-06 12:23:55 +00:00
Merge branch 'master' into racecommittee_app
This commit is contained in:
commit
da0c3f914c
61 files changed
+1094
-220
No files matched your search
@@ -476,6 +476,7 @@ if [[ "$@" == "build" ]] || [[ "$@" == "all" ]]; then
|
||||
APP_PARAMETERS="-Dmongo.host=$MONGODB_HOST -Dmongo.port=$MONGODB_PORT -Dexpedition.udp.port=$EXPEDITION_PORT -Dreplication.exchangeHost=$REPLICATION_HOST -Dreplication.exchangeName=$REPLICATION_CHANNEL"
|
||||
|
||||
echo "Using following command: mvn $extra -DargLine=\"$APP_PARAMETERS\" -fae -s $MAVEN_SETTINGS $clean install"
|
||||
echo "Maven version used: `mvn --version`"
|
||||
mvn $extra -DargLine="$APP_PARAMETERS" -fae -s $MAVEN_SETTINGS $clean install 2>&1 | tee $START_DIR/build.log
|
||||
|
||||
if [ $reporting -eq 1 ]; then
|
||||
|
||||
@@ -18,6 +18,7 @@ Like businesses, sailors need the latest information to make strategic decisions
|
||||
* Information for Developers
|
||||
* [[Cook Book|wiki/cook-book]]
|
||||
* [[OnBoarding Information|wiki/onboarding]]
|
||||
* [[Building and Deploying|wiki/building-and-deploying]]
|
||||
* [[UI Tests with Selenium|wiki/selenium-ui-tests]]
|
||||
* [[Typical Development Scenarios|wiki/typical-development-scenarios]]
|
||||
* [[Server Replication|wiki/server-replication]]
|
||||
|
||||
@@ -5,5 +5,6 @@ Bundle-SymbolicName: com.sap.sailing.domain.igtimiadapter.persistence
|
||||
Bundle-Version: 1.0.0.qualifier
|
||||
Bundle-Vendor: SAP
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
|
||||
Require-Bundle: com.sun.jersey;bundle-version="1.17.0",
|
||||
org.objectweb.asm;bundle-version="3.3.1"
|
||||
Require-Bundle: com.sap.sailing.mongodb,
|
||||
com.mongodb.driver;bundle-version="2.6.2"
|
||||
Export-Package: com.sap.sailing.domain.igtimiadapter.persistence
|
||||
+2
@@ -2,4 +2,6 @@ package com.sap.sailing.domain.igtimiadapter.persistence;
|
||||
|
||||
public interface DomainObjectFactory {
|
||||
|
||||
Iterable<String> getAccessTokens();
|
||||
|
||||
}
|
||||
+4
@@ -2,4 +2,8 @@ package com.sap.sailing.domain.igtimiadapter.persistence;
|
||||
|
||||
public interface MongoObjectFactory {
|
||||
|
||||
void storeAccessToken(String accessToken);
|
||||
|
||||
void removeAccessToken(String accessToken);
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.sap.sailing.domain.igtimiadapter.persistence;
|
||||
|
||||
import com.sap.sailing.domain.igtimiadapter.persistence.impl.PersistenceFactoryImpl;
|
||||
import com.sap.sailing.mongodb.MongoDBConfiguration;
|
||||
import com.sap.sailing.mongodb.MongoDBService;
|
||||
|
||||
public interface PersistenceFactory {
|
||||
PersistenceFactory INSTANCE = new PersistenceFactoryImpl();
|
||||
|
||||
/**
|
||||
* Obtains the domain object factory using the default persistence settings from {@link MongoDBConfiguration#getDefaultConfiguration()}.
|
||||
*/
|
||||
DomainObjectFactory getDefaultDomainObjectFactory();
|
||||
|
||||
DomainObjectFactory getDomainObjectFactory(MongoDBService mongoDbService);
|
||||
|
||||
/**
|
||||
* Obtains the Mongo object factory using the default persistence settings from {@link MongoDBConfiguration#getDefaultConfiguration()}.
|
||||
*/
|
||||
MongoObjectFactory getDefaultMongoObjectFactory();
|
||||
|
||||
MongoObjectFactory getMongoObjectFactory(MongoDBService mongoDbService);
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.sap.sailing.domain.igtimiadapter.persistence.impl;
|
||||
|
||||
public enum CollectionNames {
|
||||
IGTIMI_ACCESS_TOKENS;
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.sap.sailing.domain.igtimiadapter.persistence.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBObject;
|
||||
import com.sap.sailing.domain.igtimiadapter.persistence.DomainObjectFactory;
|
||||
|
||||
public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
private final DB db;
|
||||
|
||||
public DomainObjectFactoryImpl(DB db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<String> getAccessTokens() {
|
||||
List<String> result = new ArrayList<>();
|
||||
final DBCollection accessTokenCollection = db.getCollection(CollectionNames.IGTIMI_ACCESS_TOKENS.name());
|
||||
accessTokenCollection.ensureIndex(new BasicDBObject(FieldNames.IGTIMI_ACCESS_TOKENS_ACCESS_TOKEN.name(), 1));
|
||||
for (Object o : accessTokenCollection.find()) {
|
||||
result.add((String) ((DBObject) o).get(FieldNames.IGTIMI_ACCESS_TOKENS_ACCESS_TOKEN.name()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
package com.sap.sailing.domain.igtimiadapter.persistence.impl;
|
||||
|
||||
import com.sap.sailing.domain.igtimiadapter.persistence.DomainObjectFactory;
|
||||
|
||||
public class DomainObjectFactoryimpl implements DomainObjectFactory {
|
||||
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.sap.sailing.domain.igtimiadapter.persistence.impl;
|
||||
|
||||
public enum FieldNames {
|
||||
IGTIMI_ACCESS_TOKENS_ACCESS_TOKEN;
|
||||
}
|
||||
+24
@@ -1,7 +1,31 @@
|
||||
package com.sap.sailing.domain.igtimiadapter.persistence.impl;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.WriteConcern;
|
||||
import com.sap.sailing.domain.igtimiadapter.persistence.MongoObjectFactory;
|
||||
|
||||
public class MongoObjectFactoryImpl implements MongoObjectFactory {
|
||||
private final DB db;
|
||||
|
||||
public MongoObjectFactoryImpl(DB db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeAccessToken(String accessToken) {
|
||||
final BasicDBObject basicDBObject = getAccessTokenDBQuery(accessToken);
|
||||
db.getCollection(CollectionNames.IGTIMI_ACCESS_TOKENS.name()).update(basicDBObject, basicDBObject, /* upsert */ true, /* multi */ false, WriteConcern.SAFE);
|
||||
}
|
||||
|
||||
private BasicDBObject getAccessTokenDBQuery(String accessToken) {
|
||||
final BasicDBObject basicDBObject = new BasicDBObject(FieldNames.IGTIMI_ACCESS_TOKENS_ACCESS_TOKEN.name(), accessToken);
|
||||
return basicDBObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAccessToken(String accessToken) {
|
||||
final BasicDBObject basicDBObject = getAccessTokenDBQuery(accessToken);
|
||||
db.getCollection(CollectionNames.IGTIMI_ACCESS_TOKENS.name()).remove(basicDBObject, WriteConcern.SAFE);
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.sap.sailing.domain.igtimiadapter.persistence.impl;
|
||||
|
||||
import com.sap.sailing.domain.igtimiadapter.persistence.DomainObjectFactory;
|
||||
import com.sap.sailing.domain.igtimiadapter.persistence.MongoObjectFactory;
|
||||
import com.sap.sailing.domain.igtimiadapter.persistence.PersistenceFactory;
|
||||
import com.sap.sailing.mongodb.MongoDBConfiguration;
|
||||
import com.sap.sailing.mongodb.MongoDBService;
|
||||
|
||||
public class PersistenceFactoryImpl implements PersistenceFactory {
|
||||
|
||||
@Override
|
||||
public DomainObjectFactory getDomainObjectFactory(MongoDBService mongoDbService) {
|
||||
return new DomainObjectFactoryImpl(mongoDbService.getDB());
|
||||
}
|
||||
|
||||
@Override
|
||||
public MongoObjectFactory getMongoObjectFactory(MongoDBService mongoDbService) {
|
||||
return new MongoObjectFactoryImpl(mongoDbService.getDB());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainObjectFactory getDefaultDomainObjectFactory() {
|
||||
return getDomainObjectFactory(MongoDBConfiguration.getDefaultConfiguration().getService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public MongoObjectFactory getDefaultMongoObjectFactory() {
|
||||
return getMongoObjectFactory(MongoDBConfiguration.getDefaultConfiguration().getService());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,4 +7,5 @@ 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
|
||||
com.sap.sailing.domain.common,
|
||||
com.sap.sailing.mongodb
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.sap.sailing.domain.igtimiadapter.impl;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
|
||||
import com.sap.sailing.domain.igtimiadapter.persistence.PersistenceFactory;
|
||||
import com.sap.sailing.mongodb.MongoDBConfiguration;
|
||||
import com.sap.sailing.mongodb.MongoDBService;
|
||||
|
||||
public class WebSocketServerListTest {
|
||||
@Test
|
||||
public void testWebSocketServerList() throws IllegalStateException, ClientProtocolException, IOException, ParseException, URISyntaxException {
|
||||
final ClientImpl testAppClient = new ClientImpl("7fcdd217e0aa16090edb4ad55b09ec43b2021090e209541fc9b7003c2a2b70c6",
|
||||
"aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a", "http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback");
|
||||
MongoDBConfiguration mongoTestConfig = MongoDBConfiguration.getDefaultTestConfiguration();
|
||||
MongoDBService mongoTestService = mongoTestConfig.getService();
|
||||
final IgtimiConnectionFactory igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService),
|
||||
PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoTestService));
|
||||
Iterable<URI> serverUris = igtimiConnectionFactory.getWebsocketServers();
|
||||
assertFalse(Util.isEmpty(serverUris));
|
||||
for (URI uri : serverUris) {
|
||||
uri.getScheme().startsWith("ws");
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-4
@@ -21,6 +21,7 @@ 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.Client;
|
||||
import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
|
||||
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
|
||||
import com.sap.sailing.domain.igtimiadapter.Permission;
|
||||
@@ -31,6 +32,9 @@ 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;
|
||||
import com.sap.sailing.domain.igtimiadapter.persistence.PersistenceFactory;
|
||||
import com.sap.sailing.mongodb.MongoDBConfiguration;
|
||||
import com.sap.sailing.mongodb.MongoDBService;
|
||||
|
||||
public class SignInTest {
|
||||
private static final Logger logger = Logger.getLogger(SignInTest.class.getName());
|
||||
@@ -40,9 +44,12 @@ public class SignInTest {
|
||||
ParserConfigurationException, SAXException, ClassNotFoundException, InstantiationException,
|
||||
IllegalAccessException, ClassCastException, ParseException {
|
||||
// use the credentials of "Another Test App"
|
||||
final ClientImpl testAppClient = new ClientImpl("7fcdd217e0aa16090edb4ad55b09ec43b2021090e209541fc9b7003c2a2b70c6",
|
||||
final Client testAppClient = new ClientImpl("7fcdd217e0aa16090edb4ad55b09ec43b2021090e209541fc9b7003c2a2b70c6",
|
||||
"aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a", "http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback");
|
||||
final IgtimiConnectionFactoryImpl igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient);
|
||||
MongoDBConfiguration mongoTestConfig = MongoDBConfiguration.getDefaultTestConfiguration();
|
||||
MongoDBService mongoTestService = mongoTestConfig.getService();
|
||||
final IgtimiConnectionFactory igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService),
|
||||
PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoTestService));
|
||||
final String code = igtimiConnectionFactory.authorizeAndReturnAuthorizedCode("axel.uhl@gmx.de", "123456");
|
||||
logger.info("Igtimi OAuth code is "+code);
|
||||
assertNotNull(code);
|
||||
@@ -55,9 +62,13 @@ public class SignInTest {
|
||||
IllegalStateException, ParserConfigurationException, SAXException, ClassNotFoundException,
|
||||
InstantiationException, IllegalAccessException, ClassCastException {
|
||||
// use the credentials of "Another Test App"
|
||||
final ClientImpl testAppClient = new ClientImpl("a4cecd8593e12d43a03433a6db0eea243a411749f93c278dce6a26d4804eebd2",
|
||||
final Client testAppClient = new ClientImpl("a4cecd8593e12d43a03433a6db0eea243a411749f93c278dce6a26d4804eebd2",
|
||||
"4d66022d1ec3e2991f8053514495b61cc076ff02d664f0dc8f3df9150c3864ef", "http://1.2.3.4");
|
||||
final String code = new IgtimiConnectionFactoryImpl(testAppClient).authorizeAndReturnAuthorizedCode("axel.uhl@gmx.de", "123456");
|
||||
MongoDBConfiguration mongoTestConfig = MongoDBConfiguration.getDefaultTestConfiguration();
|
||||
MongoDBService mongoTestService = mongoTestConfig.getService();
|
||||
final IgtimiConnectionFactory igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService),
|
||||
PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoTestService));
|
||||
final String code = igtimiConnectionFactory.authorizeAndReturnAuthorizedCode("axel.uhl@gmx.de", "123456");
|
||||
logger.info("Igtimi OAuth code is "+code);
|
||||
assertNotNull(code);
|
||||
}
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.sap.sailing.domain.igtimiadapter.persistence;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
import com.sap.sailing.mongodb.MongoDBConfiguration;
|
||||
|
||||
public class IgtimiPersistenceTest {
|
||||
private MongoObjectFactory mongoObjectFactory;
|
||||
private DomainObjectFactory domainObjectFactory;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MongoDBConfiguration testDBConfig = MongoDBConfiguration.getDefaultTestConfiguration();
|
||||
mongoObjectFactory = PersistenceFactory.INSTANCE.getMongoObjectFactory(testDBConfig.getService());
|
||||
domainObjectFactory = PersistenceFactory.INSTANCE.getDomainObjectFactory(testDBConfig.getService());
|
||||
}
|
||||
@Test
|
||||
public void testStoringAndLoadingAccessTokens() {
|
||||
String accessToken = "Test Access Token";
|
||||
mongoObjectFactory.removeAccessToken(accessToken);
|
||||
assertFalse(Util.contains(domainObjectFactory.getAccessTokens(), accessToken));
|
||||
mongoObjectFactory.storeAccessToken(accessToken);
|
||||
assertTrue(Util.contains(domainObjectFactory.getAccessTokens(), accessToken));
|
||||
mongoObjectFactory.removeAccessToken(accessToken);
|
||||
assertFalse(Util.contains(domainObjectFactory.getAccessTokens(), accessToken));
|
||||
}
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
package com.sap.sailing.domain.igtimiadapter.websocket;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.StatusCode;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.igtimiadapter.Account;
|
||||
import com.sap.sailing.domain.igtimiadapter.Client;
|
||||
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
|
||||
import com.sap.sailing.domain.igtimiadapter.impl.ClientImpl;
|
||||
import com.sap.sailing.domain.igtimiadapter.impl.IgtimiConnectionFactoryImpl;
|
||||
import com.sap.sailing.domain.igtimiadapter.persistence.PersistenceFactory;
|
||||
import com.sap.sailing.mongodb.MongoDBConfiguration;
|
||||
import com.sap.sailing.mongodb.MongoDBService;
|
||||
|
||||
public class WebSocketTest {
|
||||
private static final Logger logger = Logger.getLogger(WebSocketTest.class.getName());
|
||||
|
||||
@WebSocket
|
||||
public class SimpleEchoTestSocket {
|
||||
private final CountDownLatch closeLatch;
|
||||
private Session session;
|
||||
private final List<String> stringsReceived;
|
||||
|
||||
public SimpleEchoTestSocket() {
|
||||
this.closeLatch = new CountDownLatch(1);
|
||||
stringsReceived = new ArrayList<>();
|
||||
}
|
||||
|
||||
public Session getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public void sendAndWait(String msg) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
logger.info("Sending "+msg);
|
||||
Future<Void> future = session.getRemote().sendStringByFuture(msg);
|
||||
future.get(2, TimeUnit.SECONDS);
|
||||
logger.info("Done sending "+msg);
|
||||
}
|
||||
|
||||
public boolean awaitClose(int duration, TimeUnit unit) throws InterruptedException {
|
||||
return this.closeLatch.await(duration, unit);
|
||||
}
|
||||
|
||||
@OnWebSocketClose
|
||||
public void onClose(int statusCode, String reason) {
|
||||
logger.info("Connection closed: "+statusCode+" - "+reason);
|
||||
this.closeLatch.countDown();
|
||||
}
|
||||
|
||||
@OnWebSocketConnect
|
||||
public void onConnect(Session session) {
|
||||
logger.info("Got connect: "+session);
|
||||
synchronized (this) {
|
||||
this.session = session;
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void closeSession() throws IOException {
|
||||
session.close(StatusCode.NORMAL, "I'm done");
|
||||
}
|
||||
|
||||
@OnWebSocketError
|
||||
public void onError(Throwable cause) {
|
||||
logger.log(Level.SEVERE, "Error with web socket connection", cause);
|
||||
}
|
||||
|
||||
@OnWebSocketMessage
|
||||
public void onMessage(String msg) {
|
||||
logger.info("Received "+msg);
|
||||
synchronized (this) {
|
||||
stringsReceived.add(msg);
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public List<String> getStringsReceived() {
|
||||
return stringsReceived;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleWebSockeEchoTest() throws Exception {
|
||||
String destUri = "ws://echo.websocket.org"; // wss currently doesn't seem to work with Jetty 9.0.4 WebSocket implementation
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
SimpleEchoTestSocket socket = new SimpleEchoTestSocket();
|
||||
try {
|
||||
client.start();
|
||||
URI echoUri = new URI(destUri);
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
client.connect(socket, echoUri, request);
|
||||
logger.info("Connecting to : "+echoUri);
|
||||
synchronized (socket) {
|
||||
while (socket.getSession() == null) {
|
||||
socket.wait();
|
||||
}
|
||||
}
|
||||
socket.sendAndWait("Humba Humba");
|
||||
synchronized (socket) {
|
||||
if (socket.getStringsReceived().isEmpty()) {
|
||||
socket.wait(2000); // wait for 2s for the echo response to arrive
|
||||
}
|
||||
}
|
||||
socket.closeSession();
|
||||
socket.awaitClose(5, TimeUnit.SECONDS);
|
||||
assertEquals(1, socket.getStringsReceived().size());
|
||||
assertEquals("Humba Humba", socket.getStringsReceived().get(0));
|
||||
} finally {
|
||||
try {
|
||||
client.stop();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebSocketConnect() throws Exception {
|
||||
final Client client = new ClientImpl("7fcdd217e0aa16090edb4ad55b09ec43b2021090e209541fc9b7003c2a2b70c6",
|
||||
"aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a", "http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback");
|
||||
MongoDBConfiguration mongoTestConfig = MongoDBConfiguration.getDefaultTestConfiguration();
|
||||
MongoDBService mongoTestService = mongoTestConfig.getService();
|
||||
final IgtimiConnectionFactory igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(client, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService),
|
||||
PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoTestService));
|
||||
Account account = igtimiConnectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9");
|
||||
WebSocketConnectionManager manager = new WebSocketConnectionManager(igtimiConnectionFactory, Collections.singleton("GA-EN-AAEJ"), account);
|
||||
assertNotNull(manager);
|
||||
assertTrue("Connection handshake not successful within 5s", manager.waitForConnection(5000));
|
||||
manager.disconnect();
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,19 @@ Require-Bundle: org.json.simple;bundle-version="1.1.0",
|
||||
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
|
||||
com.sap.sailing.domain.common,
|
||||
com.sap.sailing.domain.igtimiadapter.persistence
|
||||
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",
|
||||
org.apache.commons.lang,
|
||||
org.eclipse.jetty.io;version="9.0.0",
|
||||
org.eclipse.jetty.util.component;version="9.0.4",
|
||||
org.eclipse.jetty.websocket.api;version="9.0.0",
|
||||
org.eclipse.jetty.websocket.api.annotations;version="9.0.0",
|
||||
org.eclipse.jetty.websocket.api.extensions;version="9.0.0",
|
||||
org.eclipse.jetty.websocket.client;version="9.0.0",
|
||||
org.eclipse.jetty.websocket.common;version="9.0.0",
|
||||
org.osgi.framework;version="1.7.0"
|
||||
Web-ContextPath: /igtimi
|
||||
Bundle-Activator: com.sap.sailing.domain.igtimiadapter.impl.Activator
|
||||
+3
@@ -1,5 +1,8 @@
|
||||
package com.sap.sailing.domain.igtimiadapter;
|
||||
|
||||
/**
|
||||
* Represents the authorization of a {@link Client} to access the data of some {@link #getUser}.
|
||||
*/
|
||||
public interface Account {
|
||||
User getUser();
|
||||
}
|
||||
+10
-5
@@ -20,13 +20,18 @@ 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>
|
||||
* @param startTime
|
||||
* optional; may be <code>null</code>
|
||||
* @param endTime
|
||||
* optional; may be <code>null</code>
|
||||
* @param deviceIds
|
||||
* optional (may be <code>null</code>) if <code>permission</code> is {@link Permission#modify}; lists the
|
||||
* devices for which to look for resources
|
||||
* @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,
|
||||
Iterable<String> deviceIds, Iterable<String> streamIds) throws IllegalStateException,
|
||||
ClientProtocolException, IOException, ParseException;
|
||||
|
||||
/**
|
||||
|
||||
+35
@@ -2,10 +2,17 @@ package com.sap.sailing.domain.igtimiadapter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.igtimiadapter.datatypes.Type;
|
||||
@@ -64,4 +71,32 @@ public interface IgtimiConnectionFactory {
|
||||
|
||||
String getResourceDataUrl(TimePoint startTime, TimePoint endTime, Iterable<String> serialNumbers,
|
||||
Map<Type, Double> typeAndCompression, Account account);
|
||||
|
||||
Iterable<URI> getWebsocketServers() throws IllegalStateException, ClientProtocolException, IOException, ParseException, URISyntaxException;
|
||||
|
||||
HttpClient getHttpClient();
|
||||
|
||||
/**
|
||||
* Retrieves the JSON object to send in its string-serialized form to a web socket connection in order to receive
|
||||
* live data from the units whose IDs are specified by <code>unitIds</code>. The sending units are expected to
|
||||
* belong to the user account to which this factory's application client has been granted permission.
|
||||
*
|
||||
* @param account
|
||||
* represents this factory's client's permissions to access a user account's data
|
||||
* @param deviceIds
|
||||
* IDs of the transmitting units expected to be visible to the account's {@link Account#getUser() user's}
|
||||
*/
|
||||
JSONObject getWebSocketConfigurationMessage(Account account, Iterable<String> deviceIds);
|
||||
|
||||
Iterable<Account> getAllAccounts();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
String authorizeAndReturnAuthorizedCode(String userEmail, String userPassword) throws ClientProtocolException,
|
||||
IOException, IllegalStateException, ParserConfigurationException, SAXException, ClassNotFoundException,
|
||||
InstantiationException, IllegalAccessException, ClassCastException;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.sap.sailing.domain.igtimiadapter;
|
||||
|
||||
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
|
||||
|
||||
public interface LiveDataListener {
|
||||
void fixesReceived(Iterable<Fix> fixes);
|
||||
}
|
||||
+4
@@ -16,4 +16,8 @@ public class AccountImpl implements Account {
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getUser().toString();
|
||||
}
|
||||
}
|
||||
+23
-6
@@ -1,10 +1,17 @@
|
||||
package com.sap.sailing.domain.igtimiadapter.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.osgi.framework.BundleActivator;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
import com.sap.sailing.domain.igtimiadapter.Client;
|
||||
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
|
||||
import com.sap.sailing.domain.igtimiadapter.persistence.DomainObjectFactory;
|
||||
import com.sap.sailing.domain.igtimiadapter.persistence.MongoObjectFactory;
|
||||
import com.sap.sailing.domain.igtimiadapter.persistence.PersistenceFactory;
|
||||
|
||||
/**
|
||||
* Maintains data about a default {@link Client} that represents this application when interacting with the Igtimi
|
||||
@@ -17,15 +24,25 @@ import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
|
||||
*
|
||||
*/
|
||||
public class Activator implements BundleActivator {
|
||||
|
||||
private static Activator INSTANCE;
|
||||
|
||||
private static final String CLIENT_ID = "d29eae61621af3057db0e638232a027e96b1d2291b1b89a1481dfcac075b0bf4";
|
||||
private static final String CLIENT_SECRET = "537dbd14a84fcb470c91d85e8c4f8f7a356ac5ffc8727594d1bfe900ee5942ef";
|
||||
private static final String DEFAULT_CLIENT_ID = "d29eae61621af3057db0e638232a027e96b1d2291b1b89a1481dfcac075b0bf4";
|
||||
private static final String DEFAULT_CLIENT_SECRET = "537dbd14a84fcb470c91d85e8c4f8f7a356ac5ffc8727594d1bfe900ee5942ef";
|
||||
private static final String DEFAULT_CLIENT_REDIRECT_URI = "http://sapsailing.com/igtimi/oauth/v1/authorizationcallback";
|
||||
private static final String CLIENT_ID_PROPERTY_NAME = "igtimi.client.id";
|
||||
private static final String CLIENT_SECRET_PROPERTY_NAME = "igtimi.client.secret";
|
||||
private static final String CLIENT_REDIRECT_URI_PROPERTY_NAME = "igtimi.client.redirecturi";
|
||||
private IgtimiConnectionFactory connectionFactory;
|
||||
|
||||
public Activator() {
|
||||
Client client = new ClientImpl(CLIENT_ID, CLIENT_SECRET, "http://sapsailing.com");
|
||||
connectionFactory = new IgtimiConnectionFactoryImpl(client);
|
||||
public Activator() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
|
||||
final String clientId = System.getProperty(CLIENT_ID_PROPERTY_NAME, DEFAULT_CLIENT_ID);
|
||||
final String clientSecret = System.getProperty(CLIENT_SECRET_PROPERTY_NAME, DEFAULT_CLIENT_SECRET);
|
||||
final String clientRedirectUri = System.getProperty(CLIENT_REDIRECT_URI_PROPERTY_NAME, DEFAULT_CLIENT_REDIRECT_URI);
|
||||
Client client = new ClientImpl(clientId, clientSecret, clientRedirectUri);
|
||||
DomainObjectFactory domainObjectFactory = PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory();
|
||||
MongoObjectFactory mongoObjectFactory = PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory();
|
||||
connectionFactory = new IgtimiConnectionFactoryImpl(client, domainObjectFactory, mongoObjectFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -34,7 +51,7 @@ public class Activator implements BundleActivator {
|
||||
context.registerService(IgtimiConnectionFactory.class, connectionFactory, /* properties */ null);
|
||||
}
|
||||
|
||||
public static Activator getInstance() {
|
||||
public static Activator getInstance() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new Activator(); // probably non-OSGi case, as in test execution
|
||||
}
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ public class FixFactory {
|
||||
JSONArray timePointsMillis = (JSONArray) fixesJson.get("t");
|
||||
int fixIndex = 0;
|
||||
for (Object timePointMillis : timePointsMillis) {
|
||||
TimePoint timePoint = new MillisecondsTimePoint((Long) timePointMillis);
|
||||
TimePoint timePoint = new MillisecondsTimePoint(((Number) timePointMillis).longValue());
|
||||
Map<Integer, Object> valuesPerSubindex = new HashMap<>();
|
||||
int i=1;
|
||||
JSONArray values;
|
||||
|
||||
+54
-7
@@ -3,12 +3,15 @@ package com.sap.sailing.domain.igtimiadapter.impl;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
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.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
@@ -30,6 +33,7 @@ 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.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.xml.sax.Attributes;
|
||||
@@ -44,6 +48,8 @@ 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;
|
||||
import com.sap.sailing.domain.igtimiadapter.persistence.DomainObjectFactory;
|
||||
import com.sap.sailing.domain.igtimiadapter.persistence.MongoObjectFactory;
|
||||
|
||||
public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
|
||||
private static final Logger logger = Logger.getLogger(IgtimiConnectionFactoryImpl.class.getName());
|
||||
@@ -51,11 +57,21 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
|
||||
private final Map<Account, String> accessTokensByAccount;
|
||||
private Map<String, Account> accountsByEmail;
|
||||
private final Client client;
|
||||
private final MongoObjectFactory mongoObjectFactory;
|
||||
|
||||
public IgtimiConnectionFactoryImpl(Client client) {
|
||||
public IgtimiConnectionFactoryImpl(Client client, DomainObjectFactory domainObjectFactory, MongoObjectFactory mongoObjectFactory) {
|
||||
this.accessTokensByAccount = new HashMap<>();
|
||||
this.accountsByEmail = new HashMap<>();
|
||||
this.client = client;
|
||||
this.mongoObjectFactory = mongoObjectFactory;
|
||||
for (String accessToken : domainObjectFactory.getAccessTokens()) {
|
||||
try {
|
||||
registerAccountForWhichClientIsAuthorized(accessToken);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Error registering Igtimi access token "+accessToken+"; probably the access token was revoked or expired.", e);
|
||||
mongoObjectFactory.removeAccessToken(accessToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -63,6 +79,7 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
|
||||
Account account = getAccount(accessToken);
|
||||
accountsByEmail.put(account.getUser().getEmail(), account);
|
||||
accessTokensByAccount.put(account, accessToken);
|
||||
mongoObjectFactory.storeAccessToken(accessToken);
|
||||
return account;
|
||||
}
|
||||
|
||||
@@ -106,6 +123,11 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
|
||||
public Account getAccountByEmail(String eMail) {
|
||||
return accountsByEmail.get(eMail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Account> getAllAccounts() {
|
||||
return new ArrayList<Account>(accountsByEmail.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IgtimiConnection connect(Account account) {
|
||||
@@ -121,6 +143,18 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
|
||||
return getApiV1BaseUrl()+"account?"+getAccessTokenUrlParameter(account);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getWebSocketConfigurationMessage(Account account, Iterable<String> deviceIds) {
|
||||
JSONObject result = new JSONObject();
|
||||
result.put("access_token", getAccessTokenForAccount(account));
|
||||
JSONArray deviceIdsJson = new JSONArray();
|
||||
result.put("devices", deviceIdsJson);
|
||||
for (String deviceId : deviceIds) {
|
||||
deviceIdsJson.add(deviceId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsersUrl(Account account) {
|
||||
return getApiV1BaseUrl()+"users?"+getAccessTokenUrlParameter(account);
|
||||
@@ -214,12 +248,7 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
|
||||
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.
|
||||
*/
|
||||
@Override
|
||||
public String authorizeAndReturnAuthorizedCode(String userEmail, String userPassword)
|
||||
throws ClientProtocolException, IOException, IllegalStateException, ParserConfigurationException,
|
||||
SAXException, ClassNotFoundException, InstantiationException, IllegalAccessException, ClassCastException {
|
||||
@@ -335,4 +364,22 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
|
||||
authorizationResponse.getEntity().getContent().close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient getHttpClient() {
|
||||
HttpClient client = new SystemDefaultHttpClient();
|
||||
return client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<URI> getWebsocketServers() throws IllegalStateException, ClientProtocolException, IOException, ParseException, URISyntaxException {
|
||||
HttpClient client = getHttpClient();
|
||||
HttpGet getWebsocketServers = new HttpGet("https://www.igtimi.com/server_listers/web_sockets");
|
||||
JSONObject serversJson = ConnectivityUtils.getJsonFromResponse(client.execute(getWebsocketServers));
|
||||
final List<URI> result = new ArrayList<>();
|
||||
for (Object serverUrl : (JSONArray) serversJson.get("web_socket_servers")) {
|
||||
URI uri = new URI((String) serverUrl);
|
||||
result.add(uri);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+5
-11
@@ -8,7 +8,6 @@ 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;
|
||||
@@ -34,7 +33,7 @@ public class IgtimiConnectionImpl implements IgtimiConnection {
|
||||
|
||||
@Override
|
||||
public Iterable<User> getUsers() throws IllegalStateException, ClientProtocolException, IOException, ParseException {
|
||||
HttpClient client = getHttpClient();
|
||||
HttpClient client = connectionFactory.getHttpClient();
|
||||
HttpGet getUsers = new HttpGet(connectionFactory.getUsersUrl(account));
|
||||
JSONObject usersJson = ConnectivityUtils.getJsonFromResponse(client.execute(getUsers));
|
||||
final List<User> result = new ArrayList<>();
|
||||
@@ -45,16 +44,11 @@ public class IgtimiConnectionImpl implements IgtimiConnection {
|
||||
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));
|
||||
Iterable<String> deviceIds, Iterable<String> streamIds) throws IllegalStateException, ClientProtocolException, IOException, ParseException {
|
||||
HttpClient client = connectionFactory.getHttpClient();
|
||||
HttpGet getResources = new HttpGet(connectionFactory.getResourcesUrl(permission, startTime, endTime, deviceIds, streamIds, account));
|
||||
JSONObject resourcesJson = ConnectivityUtils.getJsonFromResponse(client.execute(getResources));
|
||||
final List<Resource> result = new ArrayList<>();
|
||||
for (Object userJson : (JSONArray) resourcesJson.get("resources")) {
|
||||
@@ -67,7 +61,7 @@ public class IgtimiConnectionImpl implements IgtimiConnection {
|
||||
@Override
|
||||
public Iterable<Fix> getResourceData(TimePoint startTime, TimePoint endTime,
|
||||
Iterable<String> serialNumbers, Map<Type, Double> typeAndCompression) throws IllegalStateException, ClientProtocolException, IOException, ParseException {
|
||||
HttpClient client = getHttpClient();
|
||||
HttpClient client = connectionFactory.getHttpClient();
|
||||
HttpGet getResourceData = new HttpGet(connectionFactory.getResourceDataUrl(startTime, endTime, serialNumbers, typeAndCompression, account));
|
||||
JSONObject resourceDataJson = ConnectivityUtils.getJsonFromResponse(client.execute(getResourceData));
|
||||
return new FixFactory().createFixes(resourceDataJson);
|
||||
|
||||
+5
@@ -36,5 +36,10 @@ public class UserImpl implements User {
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getFirstName()+" "+getSurname()+" <"+getEmail()+">, ID "+getId();
|
||||
}
|
||||
|
||||
}
|
||||
+12
-12
@@ -29,26 +29,26 @@ public class AuthorizationCallback {
|
||||
static final String V1_AUTHORIZATIONCALLBACK = V1 + AUTHORIZATIONCALLBACK;
|
||||
private final IgtimiConnectionFactory connectionFactory;
|
||||
|
||||
public AuthorizationCallback() {
|
||||
public AuthorizationCallback() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
|
||||
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);
|
||||
if (code != null) {
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
result = Response.status(Status.UNAUTHORIZED).entity("code parameter not found in request").build();
|
||||
logger.log(Level.SEVERE, "Authentication code not found in request");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
package com.sap.sailing.domain.igtimiadapter.websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.parser.JSONParser;
|
||||
import org.json.simple.parser.ParseException;
|
||||
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
import com.sap.sailing.domain.igtimiadapter.Account;
|
||||
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
|
||||
import com.sap.sailing.domain.igtimiadapter.LiveDataListener;
|
||||
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
|
||||
import com.sap.sailing.domain.igtimiadapter.impl.FixFactory;
|
||||
|
||||
public class WebSocketConnectionManager extends WebSocketAdapter {
|
||||
private static final Logger logger = Logger.getLogger(WebSocketConnectionManager.class.getName());
|
||||
private final IgtimiConnectionFactory connectionFactory;
|
||||
private static enum TargetState { OPEN, CLOSED };
|
||||
private TargetState targetState;
|
||||
private final WebSocketClient client;
|
||||
private final ClientUpgradeRequest request;
|
||||
private final JSONObject configurationMessage;
|
||||
private final Timer timer;
|
||||
private final Iterable<String> deviceIds;
|
||||
private final Account account;
|
||||
private final FixFactory fixFactory;
|
||||
private boolean receivedServerHeartbeatInInterval;
|
||||
private final Set<LiveDataListener> liveDataListeners;
|
||||
private TimePoint igtimiServerTimepoint;
|
||||
private TimePoint localTimepointWhenServerTimepointWasReceived;
|
||||
|
||||
public WebSocketConnectionManager(IgtimiConnectionFactory connectionFactory, Iterable<String> deviceIds, Account account) throws Exception {
|
||||
this.timer = new Timer("Timer for WebSocketConnectionManager for units "+deviceIds+" and account "+account);
|
||||
this.deviceIds = deviceIds;
|
||||
this.account = account;
|
||||
this.fixFactory = new FixFactory();
|
||||
this.connectionFactory = connectionFactory;
|
||||
this.liveDataListeners = new ConcurrentSkipListSet<>();
|
||||
client = new WebSocketClient();
|
||||
configurationMessage = connectionFactory.getWebSocketConfigurationMessage(account, deviceIds);
|
||||
request = new ClientUpgradeRequest();
|
||||
client.start();
|
||||
reconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until the web socket connection has been established and the initial configuration handshake has
|
||||
* successfully completed. Technically, this means that the Igtimi server timestamp has successfully been received
|
||||
* and parsed. {@link #getIgtimiServerTimePointAndWhenItWasReceived()} then holds a valid time point.
|
||||
*
|
||||
* @param timeoutInMillis
|
||||
* use 0 to wait indefinitely
|
||||
* @return <code>true</code> if the connection is established before the timeout occurred
|
||||
*/
|
||||
public boolean waitForConnection(long timeoutInMillis) throws InterruptedException {
|
||||
long startedToWait = System.currentTimeMillis();
|
||||
synchronized (this) {
|
||||
while (igtimiServerTimepoint == null && System.currentTimeMillis() - startedToWait < timeoutInMillis) {
|
||||
wait(timeoutInMillis);
|
||||
}
|
||||
return igtimiServerTimepoint != null;
|
||||
}
|
||||
}
|
||||
|
||||
public void addLiveDataListener(LiveDataListener listener) {
|
||||
liveDataListeners.add(listener);
|
||||
}
|
||||
|
||||
public void disconnect() throws Exception {
|
||||
targetState = TargetState.CLOSED;
|
||||
client.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketConnect(Session session) {
|
||||
super.onWebSocketConnect(session);
|
||||
logger.info("received connection "+session+" for "+this);
|
||||
try {
|
||||
getRemote().sendString(configurationMessage.toString());
|
||||
startClientHeartbeat();
|
||||
startListeningForServerHeartbeat();
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.SEVERE, "Could not send configuration package to Igtimi web socket server in "+this, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketText(String message) {
|
||||
if (logger.isLoggable(Level.FINEST)) {
|
||||
logger.finest("Received "+message+" in "+this);
|
||||
}
|
||||
if (message.equals("1")) {
|
||||
logger.fine("Received server heartbeat");
|
||||
receivedServerHeartbeatInInterval = true;
|
||||
} else if (message.startsWith("[")) {
|
||||
List<Fix> fixes = new ArrayList<>();
|
||||
try {
|
||||
JSONArray jsonArray = (JSONArray) new JSONParser().parse(message);
|
||||
for (Object o : jsonArray) {
|
||||
Util.addAll(fixFactory.createFixes((JSONObject) o), fixes);
|
||||
}
|
||||
if (logger.isLoggable(Level.FINEST)) {
|
||||
logger.finest("Received fixes"+fixes);
|
||||
}
|
||||
notifyListeners(fixes);
|
||||
} catch (ParseException e) {
|
||||
logger.log(Level.SEVERE, "Error trying to parse a web socket data package coming from Igtimi "+this, e);
|
||||
}
|
||||
} else {
|
||||
// try to parse server time stamp in response to the configuration message
|
||||
synchronized (this) {
|
||||
igtimiServerTimepoint = new MillisecondsTimePoint(Long.valueOf(message));
|
||||
localTimepointWhenServerTimepointWasReceived = MillisecondsTimePoint.now();
|
||||
logger.info("Received server timestamp "+igtimiServerTimepoint);
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Pair<TimePoint, TimePoint> getIgtimiServerTimePointAndWhenItWasReceived() {
|
||||
return new Pair<TimePoint, TimePoint>(igtimiServerTimepoint, localTimepointWhenServerTimepointWasReceived);
|
||||
}
|
||||
|
||||
private void notifyListeners(List<Fix> fixes) {
|
||||
for (LiveDataListener listener : liveDataListeners) {
|
||||
listener.fixesReceived(fixes);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketClose(int statusCode, String reason) {
|
||||
if (targetState == TargetState.OPEN) {
|
||||
try {
|
||||
reconnect();
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Couldn't reconnect to Igtimi web socket in "+this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketError(Throwable cause) {
|
||||
logger.log(Level.SEVERE, "Error trying to open Igtimi web socket in "+this, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Web Socket Connection Manager for devices "+deviceIds+" and account "+account+" with web socket session "+getSession();
|
||||
}
|
||||
|
||||
private void startListeningForServerHeartbeat() {
|
||||
timer.scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (!receivedServerHeartbeatInInterval) {
|
||||
logger.info("Didn't receive server heartbeat in interval for "+this+". Reconnecting...");
|
||||
reconnect();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.WARNING, "Error with server heartbeat in "+this, e);
|
||||
}
|
||||
}
|
||||
}, /* wait for 45s for first execution */ 45000l, /* and check again every 45s*/ 45000l);
|
||||
}
|
||||
|
||||
private void startClientHeartbeat() {
|
||||
timer.scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
logger.fine("Sending client heartbeat");
|
||||
getRemote().sendString("1");
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.WARNING, "Couldn't send heartbeat to Igtimi web socket session in "+this, e);
|
||||
}
|
||||
}
|
||||
}, /* delay */ 0, /* send ping message every other minute; has to be at least every five minutes, so this should be safe */ 120000l);
|
||||
}
|
||||
|
||||
public void reconnect() throws IOException, IllegalStateException, ParseException, URISyntaxException {
|
||||
IOException lastException = null;
|
||||
for (URI uri : connectionFactory.getWebsocketServers()) {
|
||||
try {
|
||||
if (uri.getScheme().equals("ws")) { // as Jetty 9.0.4 currently doesn't seem to support wss, explicitly
|
||||
// look for ws connectivity
|
||||
logger.log(Level.INFO, "Trying to connect to " + uri + " for " + this);
|
||||
client.connect(this, uri, request);
|
||||
logger.log(Level.INFO, "Successfully connected to " + uri + " for " + this);
|
||||
lastException = null;
|
||||
break; // successfully connected
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.INFO, "Couldn't connect to "+uri+" for "+this, e);
|
||||
lastException = e;
|
||||
}
|
||||
}
|
||||
if (lastException != null) {
|
||||
throw lastException;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -386,8 +386,7 @@ public class StoreAndForward implements Runnable {
|
||||
int sailMasterPort = Integer.valueOf(args[i++]);
|
||||
int clientPort = Integer.valueOf(args[i++]);
|
||||
|
||||
MongoDBService mongoDBService = MongoDBService.INSTANCE;
|
||||
mongoDBService.setConfiguration(MongoDBConfiguration.getDefaultConfiguration());
|
||||
MongoDBService mongoDBService = MongoDBConfiguration.getDefaultConfiguration().getService();
|
||||
SwissTimingAdapterPersistence swissTimingAdapterPersistence = SwissTimingAdapterPersistence.INSTANCE;
|
||||
if (hostname == null) {
|
||||
new StoreAndForward(sailMasterPort, clientPort, SwissTimingFactory.INSTANCE, swissTimingAdapterPersistence, mongoDBService);
|
||||
|
||||
+4
-4
@@ -40,12 +40,12 @@ public class SwissTimingReplayAdapterServiceTest {
|
||||
assertEquals("Elliott", race_42.getBoatClass());
|
||||
assertEquals("19", race_42.getFlightNumber());
|
||||
assertEquals(swissTimingUrlText, race_42.getJsonUrl());
|
||||
assertEquals("live.ota.st-sportservice.com/Data/Log?_rsc=SAW010955&_date=02.08.2012", race_42.getLink());
|
||||
assertEquals("live.ota.st-sportservice.com/Data/Log?_rsc=SAW010955&_date=02.08.2012&_start=0", race_42.getLink());
|
||||
assertEquals("Match 55", race_42.getName());
|
||||
assertEquals("6264", race_42.getRaceId());
|
||||
assertEquals("SAW010955", race_42.getRsc());
|
||||
assertEquals("02.08.2012 15:30", swissTimingReplayService.getStartTimeFormat().format(races.get(42).getStartTime()));
|
||||
assertEquals("live.ota.st-sportservice.com/Data/Log?_rsc=SAM009901&_date=30.07.2012", races.get(races.size() - 1).getLink());
|
||||
assertEquals("live.ota.st-sportservice.com/Data/Log?_rsc=SAM009901&_date=30.07.2012&_start=0", races.get(races.size() - 1).getLink());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -73,7 +73,7 @@ public class SwissTimingReplayAdapterServiceTest {
|
||||
public void testRaceData_SAW005905_20120805_EqualsOnlineVersion() throws Exception {
|
||||
byte[] localCopy = read(getClass().getResourceAsStream("/SAW005905.20120805.replay"));
|
||||
byte[] onlineCopy = read((InputStream) new URL(
|
||||
"http://live.ota.st-sportservice.com/Data/Log?_rsc=SAW005905&_date=05.08.2012").getContent());
|
||||
"http://live.ota.st-sportservice.com/Data/Log?_rsc=SAW005905&_date=05.08.2012&_start=0").getContent());
|
||||
assertArrayEquals(localCopy, onlineCopy);
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ public class SwissTimingReplayAdapterServiceTest {
|
||||
public void testRaceData_SAW005905_20120805_online() throws Exception {
|
||||
SwissTimingReplayTestListener replayCountListener = new SwissTimingReplayTestListener();
|
||||
byte[] onlineCopy = read((InputStream) new URL(
|
||||
"http://live.ota.st-sportservice.com/Data/Log?_rsc=SAW005905&_date=05.08.2012").getContent());
|
||||
"http://live.ota.st-sportservice.com/Data/Log?_rsc=SAW005905&_date=05.08.2012&_start=0").getContent());
|
||||
new SwissTimingReplayParserImpl().readData(new ByteArrayInputStream(onlineCopy), replayCountListener);
|
||||
assertEquals(0, replayCountListener.keyFrameIndexSum);
|
||||
assertEquals(715, replayCountListener.keyFrameIndexPositionCount);
|
||||
|
||||
+1
-1
@@ -106,7 +106,7 @@ public class SwissTimingReplayServiceImpl implements SwissTimingReplayService {
|
||||
(String) jsonRaceEntry.get("flight_number"), (String) jsonRaceEntry.get("race_id"),
|
||||
(String) jsonRaceEntry.get("rsc"), (String) jsonRaceEntry.get("name"),
|
||||
(String) jsonRaceEntry.get("class"), startTime,
|
||||
(String) jsonRaceEntry.get("link"));
|
||||
(String) jsonRaceEntry.get("link")+"&_start=0");
|
||||
result.add(replayRace);
|
||||
}
|
||||
return result;
|
||||
|
||||
-7
@@ -391,11 +391,4 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter {
|
||||
trackedRace.setStatus(new TrackedRaceStatusImpl(newStatus, progress));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eot() {
|
||||
for (DynamicTrackedRace trackedRace : getTrackedRaces()) {
|
||||
trackedRace.setStatus(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.FINISHED, 1.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -29,4 +29,9 @@ public class TrackedRaceStatusImpl implements TrackedRaceStatus {
|
||||
public double getLoadingProgress() {
|
||||
return loadingProgress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ""+status+" ("+loadingProgress+")";
|
||||
}
|
||||
}
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
package com.sap.sailing.gwt.ui.test;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.base.impl.BoatImpl;
|
||||
import com.sap.sailing.gwt.ui.client.shared.filter.AbstractListFilter;
|
||||
|
||||
public class AbstractListFilterTest {
|
||||
|
||||
AbstractListFilter<BoatImpl> als = new AbstractListFilter<BoatImpl>() {
|
||||
@Override
|
||||
public Iterable<String> getStrings(BoatImpl t) {
|
||||
return Arrays.asList(t.getName(), t.getSailID());
|
||||
}
|
||||
};
|
||||
ArrayList<BoatImpl> all = new ArrayList<BoatImpl>();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
all.add(new BoatImpl("Race 1", null, "Kiel"));
|
||||
all.add(new BoatImpl("Race 2", null, "Kiel"));
|
||||
all.add(new BoatImpl("Race 3", null, "Kiel"));
|
||||
all.add(new BoatImpl("Race 1", null, "Wannsee"));
|
||||
all.add(new BoatImpl("Race 2", null, "Wannsee"));
|
||||
all.add(new BoatImpl("Final", null, "Wannsee"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
assertTrue(als.applyFilter("Race 2", all).size() == 2);
|
||||
assertTrue(als.applyFilter("3", all).size() == 1 && als.applyFilter("3", all).get(0).getName() == "Race 3");
|
||||
assertTrue(als.applyFilter("1 Wannsee", all).size() == 1 && als.applyFilter("1 Wannsee", all).get(0).getName() == "Race 1");
|
||||
assertTrue(als.applyFilter("Wannsee 3", all).size() == 0);
|
||||
assertTrue(als.applyFilter("Race Race", all).size() == 5);
|
||||
assertTrue(als.applyFilter("R", all).size() == 5);
|
||||
assertTrue(als.applyFilter("i", all).size() == 4);
|
||||
}
|
||||
}
|
||||
+1
-6
@@ -152,11 +152,6 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl
|
||||
}
|
||||
});
|
||||
filterablePanelRaces = new AbstractFilterablePanel<RaceDTO>(lblFilterRaces, allRaces, raceTable, raceList) {
|
||||
@Override
|
||||
public void applyFilter() {
|
||||
super.applyFilter();
|
||||
onRaceSelectionChange(raceSelectionProvider.getSelectedRaces());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSearchableStrings(RaceDTO t) {
|
||||
@@ -201,7 +196,7 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl
|
||||
columnSortHandler.setComparator(regattaNameColumn, new Comparator<RaceDTO>() {
|
||||
@Override
|
||||
public int compare(RaceDTO r1, RaceDTO r2) {
|
||||
return r1.getRegattaName().compareTo(r2.getRegattaName());
|
||||
return new NaturalComparator().compare(r1.getRegattaName(), r2.getRegattaName());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+2
-1
@@ -26,6 +26,7 @@ import com.google.gwt.view.client.SelectionChangeEvent;
|
||||
import com.google.gwt.view.client.SelectionModel;
|
||||
import com.sap.sailing.domain.common.RegattaIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaName;
|
||||
import com.sap.sailing.domain.common.impl.NaturalComparator;
|
||||
import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback;
|
||||
import com.sap.sailing.gwt.ui.client.ErrorReporter;
|
||||
import com.sap.sailing.gwt.ui.client.RegattaDisplayer;
|
||||
@@ -132,7 +133,7 @@ public class RegattaListComposite extends Composite implements RegattaDisplayer
|
||||
columnSortHandler.setComparator(regattaNameColumn, new Comparator<RegattaDTO>() {
|
||||
@Override
|
||||
public int compare(RegattaDTO r1, RegattaDTO r2) {
|
||||
return r1.getName().compareTo(r2.getName());
|
||||
return new NaturalComparator().compare(r1.getName(), r2.getName());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -287,7 +287,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
|
||||
result.setComparator(nameColumn, new Comparator<SwissTimingRaceRecordDTO>() {
|
||||
@Override
|
||||
public int compare(SwissTimingRaceRecordDTO o1, SwissTimingRaceRecordDTO o2) {
|
||||
return o1.ID.compareTo(o2.ID);
|
||||
return new NaturalComparator().compare(o1.ID, o2.ID);
|
||||
}
|
||||
});
|
||||
result.setComparator(trackingStartColumn, new Comparator<SwissTimingRaceRecordDTO>() {
|
||||
|
||||
+2
-1
@@ -33,6 +33,7 @@ import com.google.gwt.view.client.ListDataProvider;
|
||||
import com.google.gwt.view.client.MultiSelectionModel;
|
||||
import com.sap.sailing.domain.common.RegattaIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaName;
|
||||
import com.sap.sailing.domain.common.impl.NaturalComparator;
|
||||
import com.sap.sailing.gwt.ui.client.ErrorReporter;
|
||||
import com.sap.sailing.gwt.ui.client.RaceSelectionModel;
|
||||
import com.sap.sailing.gwt.ui.client.RegattaRefresher;
|
||||
@@ -257,7 +258,7 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane
|
||||
result.setComparator(nameColumn, new Comparator<SwissTimingReplayRaceDTO>() {
|
||||
@Override
|
||||
public int compare(SwissTimingReplayRaceDTO o1, SwissTimingReplayRaceDTO o2) {
|
||||
return o1.name.compareTo(o2.name);
|
||||
return new NaturalComparator().compare(o1.name, o2.name);
|
||||
}
|
||||
});
|
||||
result.setComparator(boatClassColumn, new Comparator<SwissTimingReplayRaceDTO>() {
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package com.sap.sailing.gwt.ui.client.shared.filter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AbstractListFilter<T> {
|
||||
|
||||
/**
|
||||
* Subclasses must implement this to extract the strings from an object of type <code>T</code> based on which the
|
||||
* filter performs its filtering
|
||||
*
|
||||
* @param t
|
||||
* the object from which to extract the searchable strings
|
||||
* @return the searchable strings
|
||||
*/
|
||||
public abstract Iterable<String> getStrings(T t);
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a list based on the contents of {@link #all} and the current search phrase {@link #text}.
|
||||
*/
|
||||
public List<T> applyFilter(String text, Iterable<T> all) {
|
||||
List<String> inputText = Arrays.asList(text.split(" "));
|
||||
List<T> sortedList = new ArrayList<T>();
|
||||
if (all != null) {
|
||||
if (inputText != null && !inputText.isEmpty()) {
|
||||
for (T t : all) {
|
||||
if (containsText(t, inputText)) {
|
||||
sortedList.add(t);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (T t : all) {
|
||||
sortedList.add(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sortedList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if <code>wordsToFilter</code> contain a value of the <code>valuesToCheck</code>
|
||||
*
|
||||
* @param wordsToFilter
|
||||
* the words to filter on
|
||||
* @param valuesToCheck
|
||||
* the values to check for.
|
||||
* @return <code>true</code> if the <code>valuesToCheck</code> contains all <code>wordsToFilter</code>,
|
||||
* <code>false</code> if not
|
||||
*/
|
||||
private boolean containsText(T obj, List<String> wordsToFilter) {
|
||||
boolean failed = false;
|
||||
for (String word : wordsToFilter) {
|
||||
String textAsUppercase = word.toUpperCase().trim();
|
||||
failed = true;
|
||||
for (String s : getStrings(obj)) {
|
||||
if (s != null && s.toUpperCase().contains(textAsUppercase)) {
|
||||
failed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (failed) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+31
-69
@@ -1,8 +1,5 @@
|
||||
package com.sap.sailing.gwt.ui.client.shared.panels;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gwt.event.dom.client.KeyUpEvent;
|
||||
import com.google.gwt.event.dom.client.KeyUpHandler;
|
||||
import com.google.gwt.user.cellview.client.CellTable;
|
||||
@@ -11,16 +8,17 @@ import com.google.gwt.user.client.ui.HorizontalPanel;
|
||||
import com.google.gwt.user.client.ui.Label;
|
||||
import com.google.gwt.user.client.ui.TextBox;
|
||||
import com.google.gwt.view.client.ListDataProvider;
|
||||
import com.sap.sailing.gwt.ui.client.shared.filter.AbstractListFilter;
|
||||
|
||||
/**
|
||||
* This Panel contains a label and a text box. Text entered into the text box filters the {@link CellTable} passed to
|
||||
* the constructor by adjusting the cell table's {@link ListDataProvider}'s contents and then sorting the table again
|
||||
* according the the sorting criteria currently active (the sorting is the only reason why the {@link CellTable}
|
||||
* actually needs to be known to an instance of this class). To be initiated the method
|
||||
* {@link #getSearchableStrings(Object)} has to be defined, which gets those Strings from a <code>T</code> that should
|
||||
* be considered when filtering, e.g. name or boatClass. The cell table can be sorted independently from the text box
|
||||
* (e.g. after adding new objects) by calling the method {@link #updateAll(Iterable)} which then runs the filter over
|
||||
* the new selection.
|
||||
* the constructor by adjusting the cell table's {@link ListDataProvider}'s contents using the {@link
|
||||
* applyFilter(String, List)} of the {@link AbstractListFilter} and then sorting the table again according the the
|
||||
* sorting criteria currently active (the sorting is the only reason why the {@link CellTable} actually needs to be
|
||||
* known to an instance of this class). To be initiated the method {@link #getSearchableStrings(Object)} has to be
|
||||
* defined, which gets those Strings from a <code>T</code> that should be considered when filtering, e.g. name or
|
||||
* boatClass. The cell table can be sorted independently from the text box (e.g. after adding new objects) by calling
|
||||
* the method {@link #updateAll(Iterable)} which then runs the filter over the new selection.
|
||||
*
|
||||
* @param <T>
|
||||
* @author Nicolas Klose, Axel Uhl
|
||||
@@ -31,94 +29,58 @@ public abstract class AbstractFilterablePanel<T> extends HorizontalPanel {
|
||||
private final CellTable<T> display;
|
||||
private final ListDataProvider<T> filtered;
|
||||
private final TextBox textBox;
|
||||
private final AbstractListFilter<T> filterer = new AbstractListFilter<T>(){
|
||||
|
||||
public AbstractFilterablePanel(Label label, Iterable<T> all, CellTable<T> display, ListDataProvider<T> filtered) {
|
||||
@Override
|
||||
public Iterable<String> getStrings(T t) {
|
||||
return getSearchableStrings(t);
|
||||
}
|
||||
};
|
||||
|
||||
public AbstractFilterablePanel(Label label, Iterable<T> all, CellTable<T> display, final ListDataProvider<T> filtered) {
|
||||
setSpacing(5);
|
||||
this.all = all;
|
||||
this.display = display;
|
||||
this.filtered = filtered;
|
||||
this.textBox = new TextBox();
|
||||
this.all = all;
|
||||
add(label);
|
||||
add(getTextBox());
|
||||
getTextBox().addKeyUpHandler(new KeyUpHandler() {
|
||||
@Override
|
||||
public void onKeyUp(KeyUpEvent event) {
|
||||
applyFilter();
|
||||
filter();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses must implement this to extract the strings from an object of type <code>T</code> based on which the
|
||||
* search box performs its filtering
|
||||
* filter performs its filtering
|
||||
*
|
||||
* @param t
|
||||
* the object from which to extract the searchable strings
|
||||
* @return the searchable strings
|
||||
*/
|
||||
protected abstract Iterable<String> getSearchableStrings(T t);
|
||||
public abstract Iterable<String> getSearchableStrings(T t);
|
||||
|
||||
/**
|
||||
* Updates the set of all objects to be shown in the table and applies the search filter to update the
|
||||
* table view.
|
||||
*/
|
||||
public void updateAll(Iterable<T> all) {
|
||||
|
||||
public void updateAll(Iterable<T> all){
|
||||
this.all = all;
|
||||
applyFilter();
|
||||
filter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconstructs the {@link #filtered} list contents based on the contents of {@link #all} as provided through
|
||||
* {@link #updateAll(Iterable)} and the current search phrase entered in the search {@link #textBox text box}. After
|
||||
* filtering, the original sort order is restored.
|
||||
*/
|
||||
public void applyFilter() {
|
||||
String text = getTextBox().getText();
|
||||
List<String> inputText = Arrays.asList(text.split(" "));
|
||||
filtered.getList().clear();
|
||||
if (all != null) {
|
||||
if (text != null && !text.isEmpty()) {
|
||||
for (T t : all) {
|
||||
if (filter(t, inputText)) {
|
||||
filtered.getList().add(t);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (T t : all) {
|
||||
filtered.getList().add(t);
|
||||
}
|
||||
}
|
||||
// now sort again according to selected criterion
|
||||
ColumnSortEvent.fire(display, display.getColumnSortList());
|
||||
}
|
||||
|
||||
public void filter(){
|
||||
filtered.getList().clear();
|
||||
filtered.getList().addAll(filterer.applyFilter(getTextBox().getText(), all));
|
||||
sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if <code>wordsToFilter</code> contain a value of the <code>valuesToCheck</code>
|
||||
*
|
||||
* @param wordsToFilter
|
||||
* the words to filter on
|
||||
* @param valuesToCheck
|
||||
* the values to check for. These values contain the values of the current rows.
|
||||
* @return <code>true</code> if the <code>valuesToCheck</code> contains all <code>wordsToFilter</code>,
|
||||
* <code>false</code> if not
|
||||
*/
|
||||
private boolean filter(T obj, List<String> wordsToFilter) {
|
||||
boolean failed = false;
|
||||
for (String word : wordsToFilter) {
|
||||
String textAsUppercase = word.toUpperCase().trim();
|
||||
failed = true;
|
||||
for (String s : getSearchableStrings(obj)) {
|
||||
if (s != null && s.toUpperCase().contains(textAsUppercase)) {
|
||||
failed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (failed) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
private void sort(){
|
||||
ColumnSortEvent.fire(display, display.getColumnSortList());
|
||||
}
|
||||
|
||||
public TextBox getTextBox() {
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ public class RaceMapZoomSettings {
|
||||
}
|
||||
|
||||
public LatLngBounds calculateNewBounds(RaceMap forMap) {
|
||||
return calculator.calculateNewBounds(forMap);
|
||||
return calculator == null ? null : calculator.calculateNewBounds(forMap);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+25
-23
@@ -2178,32 +2178,34 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
logger.info("replaySwissTimingRace for regatta "+regattaIdentifier+" for races "+replayRaceDTOs);
|
||||
Regatta regatta;
|
||||
for (SwissTimingReplayRaceDTO replayRaceDTO : replayRaceDTOs) {
|
||||
if (regattaIdentifier == null) {
|
||||
String boatClass = replayRaceDTO.boat_class;
|
||||
for (String genderIndicator : new String[] { "Man", "Woman", "Men", "Women", "M", "W" }) {
|
||||
Pattern p = Pattern.compile("(( - )|-| )" + genderIndicator + "$");
|
||||
Matcher m = p.matcher(boatClass.trim());
|
||||
if (m.find()) {
|
||||
boatClass = boatClass.trim().substring(0, m.start(1));
|
||||
break;
|
||||
try {
|
||||
if (regattaIdentifier == null) {
|
||||
String boatClass = replayRaceDTO.boat_class;
|
||||
for (String genderIndicator : new String[] { "Man", "Woman", "Men", "Women", "M", "W" }) {
|
||||
Pattern p = Pattern.compile("(( - )|-| )" + genderIndicator + "$");
|
||||
Matcher m = p.matcher(boatClass.trim());
|
||||
if (m.find()) {
|
||||
boatClass = boatClass.trim().substring(0, m.start(1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
regatta = getService().createRegatta(
|
||||
replayRaceDTO.rsc,
|
||||
boatClass.trim(),
|
||||
RegattaImpl.getDefaultName(replayRaceDTO.rsc, replayRaceDTO.boat_class),
|
||||
Collections.singletonList(new SeriesImpl(LeaderboardNameConstants.DEFAULT_SERIES_NAME,
|
||||
/* isMedal */false, Collections.singletonList(new FleetImpl(
|
||||
LeaderboardNameConstants.DEFAULT_FLEET_NAME)),
|
||||
/* race column names */new ArrayList<String>(), getService())), false,
|
||||
baseDomainFactory.createScoringScheme(ScoringSchemeType.LOW_POINT), null);
|
||||
// TODO: is course area relevant for swiss timing replay?
|
||||
} else {
|
||||
regatta = getService().getRegatta(regattaIdentifier);
|
||||
}
|
||||
regatta = getService().createRegatta(
|
||||
replayRaceDTO.rsc,
|
||||
boatClass.trim(),
|
||||
RegattaImpl.getDefaultName(replayRaceDTO.rsc, replayRaceDTO.boat_class),
|
||||
Collections.singletonList(new SeriesImpl(
|
||||
LeaderboardNameConstants.DEFAULT_SERIES_NAME,
|
||||
/* isMedal */false,
|
||||
Collections.singletonList(new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME)),
|
||||
/* race column names */ new ArrayList<String>(), getService())),
|
||||
false,
|
||||
baseDomainFactory.createScoringScheme(ScoringSchemeType.LOW_POINT), null);
|
||||
//TODO: is course area relevant for swiss timing replay?
|
||||
} else {
|
||||
regatta = getService().getRegatta(regattaIdentifier);
|
||||
getSwissTimingReplayService().loadRaceData(replayRaceDTO.link, regatta, getService());
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Error trying to load SwissTimingReplay race " + replayRaceDTO, e);
|
||||
}
|
||||
getSwissTimingReplayService().loadRaceData(replayRaceDTO.link, regatta, getService());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -40,14 +40,15 @@ import com.sap.sailing.domain.common.RegattaNameAndRaceName;
|
||||
import com.sap.sailing.domain.common.dto.FleetDTO;
|
||||
import com.sap.sailing.domain.common.dto.PlacemarkOrderDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
|
||||
import com.sap.sailing.domain.common.impl.NaturalComparator;
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
import com.sap.sailing.gwt.ui.adminconsole.LeaderboardConfigPanel.AnchorCell;
|
||||
import com.sap.sailing.gwt.ui.client.ErrorReporter;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.client.Timer;
|
||||
import com.sap.sailing.gwt.ui.client.URLEncoder;
|
||||
import com.sap.sailing.gwt.ui.client.Timer.PlayModes;
|
||||
import com.sap.sailing.gwt.ui.client.URLEncoder;
|
||||
import com.sap.sailing.gwt.ui.shared.EventDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
|
||||
|
||||
@@ -295,7 +296,7 @@ public class EventOverviewPanel extends FormPanel {
|
||||
eventsListHandler.setComparator(eventNameColumn, new Comparator<EventDTO>() {
|
||||
@Override
|
||||
public int compare(EventDTO e1, EventDTO e2) {
|
||||
return e1.getName().compareTo(e2.getName());
|
||||
return new NaturalComparator().compare(e1.getName(), e2.getName());
|
||||
}
|
||||
});
|
||||
eventsListHandler.setComparator(eventVenueColumn, new Comparator<EventDTO>() {
|
||||
|
||||
+1
-2
@@ -20,8 +20,7 @@ public abstract class AbstractMongoDBTest {
|
||||
|
||||
public AbstractMongoDBTest() throws UnknownHostException, MongoException {
|
||||
dbConfiguration = MongoDBConfiguration.getDefaultTestConfiguration();
|
||||
service = MongoDBService.INSTANCE;
|
||||
service.setConfiguration(getDBConfiguration());
|
||||
service = getDBConfiguration().getService();
|
||||
mongo = newMongo();
|
||||
db = service.getDB();
|
||||
}
|
||||
|
||||
+1
-2
@@ -20,8 +20,7 @@ public abstract class AbstractMongoDBTest {
|
||||
|
||||
public AbstractMongoDBTest() throws UnknownHostException, MongoException {
|
||||
dbConfiguration = MongoDBConfiguration.getDefaultTestConfiguration();
|
||||
service = MongoDBService.INSTANCE;
|
||||
service.setConfiguration(getDBConfiguration());
|
||||
service = getDBConfiguration().getService();
|
||||
mongo = newMongo();
|
||||
db = service.getDB();
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.util.logging.Logger;
|
||||
import org.osgi.framework.BundleActivator;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
import com.sap.sailing.mongodb.internal.MongoDBServiceImpl;
|
||||
|
||||
public class Activator implements BundleActivator {
|
||||
private static final Logger logger = Logger.getLogger(Activator.class.getName());
|
||||
|
||||
@@ -35,7 +37,7 @@ public class Activator implements BundleActivator {
|
||||
}
|
||||
|
||||
MongoDBConfiguration newConfiguration = new MongoDBConfiguration(hostName, port, databaseName);
|
||||
MongoDBService.INSTANCE.setConfiguration(newConfiguration);
|
||||
((MongoDBServiceImpl) MongoDBService.INSTANCE).setConfiguration(newConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.sap.sailing.mongodb;
|
||||
|
||||
import com.sap.sailing.mongodb.internal.MongoDBServiceImpl;
|
||||
|
||||
/**
|
||||
* A readonly Mongo Database connection configuration
|
||||
* @author Frank
|
||||
@@ -69,6 +71,13 @@ public class MongoDBConfiguration {
|
||||
this.port = port;
|
||||
this.databaseName = databaseName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a DB service for this configuration
|
||||
*/
|
||||
public MongoDBService getService() {
|
||||
return new MongoDBServiceImpl(this);
|
||||
}
|
||||
|
||||
public String getHostName() {
|
||||
return hostName;
|
||||
|
||||
@@ -11,27 +11,19 @@ public interface MongoDBService {
|
||||
|
||||
MongoDBConfiguration getConfiguration();
|
||||
|
||||
void setConfiguration(MongoDBConfiguration configuration);
|
||||
|
||||
/**
|
||||
* Should be called before accessing any collection.
|
||||
*
|
||||
* @param registerForInterface
|
||||
* The interface to register the collection for. This might be a
|
||||
* placeholder Interface that is used by multiple classes
|
||||
* accessing one and the same collection, demonstrating that they
|
||||
* have knowledge of each other. This is resolved to a String
|
||||
* representing the fully qualified class name to avoid issues
|
||||
* when bundles are restarted, in which case the identity of
|
||||
* class objects changes, but the fully qualified class name
|
||||
* String remains the same.
|
||||
* @param collectionName
|
||||
* @throws AlreadyRegisteredException
|
||||
* Is thrown if the collection name has already been registered
|
||||
* for another class. This shouldn't happen in a productive
|
||||
* version, but should make aware of the problem while
|
||||
* developing.
|
||||
*/
|
||||
void registerExclusively(Class<?> registerForInterface,
|
||||
String collectionName) throws AlreadyRegisteredException;
|
||||
/**
|
||||
* Should be called before accessing any collection.
|
||||
*
|
||||
* @param registerForInterface
|
||||
* The interface to register the collection for. This might be a placeholder Interface that is used by
|
||||
* multiple classes accessing one and the same collection, demonstrating that they have knowledge of each
|
||||
* other. This is resolved to a String representing the fully qualified class name to avoid issues when
|
||||
* bundles are restarted, in which case the identity of class objects changes, but the fully qualified
|
||||
* class name String remains the same.
|
||||
* @param collectionName
|
||||
* @throws AlreadyRegisteredException
|
||||
* Is thrown if the collection name has already been registered for another class. This shouldn't happen
|
||||
* in a productive version, but should make aware of the problem while developing.
|
||||
*/
|
||||
void registerExclusively(Class<?> registerForInterface, String collectionName) throws AlreadyRegisteredException;
|
||||
}
|
||||
+1
-2
@@ -39,8 +39,7 @@ public class DatabaseHelper {
|
||||
|
||||
public DatabaseHelper(MongoDBConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
this.service = MongoDBService.INSTANCE;
|
||||
this.service.setConfiguration(configuration);
|
||||
this.service = getDBConfiguration().getService();
|
||||
this.database = this.service.getDB();
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -41,8 +41,7 @@ public abstract class AbstractJsonExportTest {
|
||||
protected static SimpleDateFormat TIMEPOINT_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
|
||||
|
||||
public void setUp() {
|
||||
service = MongoDBService.INSTANCE;
|
||||
service.setConfiguration(MongoDBConfiguration.getDefaultTestConfiguration());
|
||||
service = MongoDBConfiguration.getDefaultTestConfiguration().getService();
|
||||
service.getDB().dropDatabase();
|
||||
|
||||
racingEventService = new RacingEventServiceImpl();
|
||||
|
||||
+1
-2
@@ -36,8 +36,7 @@ public abstract class AbstractJaxRsApiTest {
|
||||
protected static SimpleDateFormat TIMEPOINT_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
|
||||
|
||||
public void setUp() {
|
||||
service = MongoDBService.INSTANCE;
|
||||
service.setConfiguration(MongoDBConfiguration.getDefaultTestConfiguration());
|
||||
service = MongoDBConfiguration.getDefaultTestConfiguration().getService();
|
||||
service.getDB().dropDatabase();
|
||||
|
||||
racingEventService = new RacingEventServiceImpl();
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console -clean"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dexpedition.udp.port=2010 -Xmx4096m -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -Dkiwo.results=${project_loc:com.sap.sailing.kiworesultimport.test}/resources -Dpersistentcompetitors.clear=false -XX:+UseMembar"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dexpedition.udp.port=2010 -Xmx4096m -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -Dkiwo.results=${project_loc:com.sap.sailing.kiworesultimport.test}/resources -Dpersistentcompetitors.clear=false -XX:+UseMembar -Digtimi.client.id=7fcdd217e0aa16090edb4ad55b09ec43b2021090e209541fc9b7003c2a2b70c6 -Digtimi.client.secret=aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a -Digtimi.client.redirecturi=http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc}"/>
|
||||
<stringAttribute key="pde.version" value="3.3"/>
|
||||
<stringAttribute key="profilingTraceType-ALLOCATION_TRACE" value="KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%INCREASE_COUNT%CTX_KEY%8192%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ADAPTIVE%CTX_KEY%false%CTX_ENTRY%KEY_MIN_SIZE%CTX_KEY%32%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%CLASS_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_MAX_SIZE%CTX_KEY%65536%CTX_ENTRY%KEY_INC_LINE_NRS%CTX_KEY%true%CTX_ENTRY%KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%"/>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console -clean"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dfile.encoding=cp1252 -Dexpedition.udp.port=2010 -Xmx6000m -Dhttp.proxyHost=proxy.wdf.sap.corp -Dhttp.proxyPort=8080 -Dhttps.proxyHost=proxy -Dhttps.proxyPort=8080 -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -Dkiwo.results=${project_loc:com.sap.sailing.kiworesultimport.test}/resources -Dpersistentcompetitors.clear=false -XX:+UseMembar"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dfile.encoding=cp1252 -Dexpedition.udp.port=2010 -Xmx6000m -Dhttp.proxyHost=proxy.wdf.sap.corp -Dhttp.proxyPort=8080 -Dhttps.proxyHost=proxy -Dhttps.proxyPort=8080 -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -Dkiwo.results=${project_loc:com.sap.sailing.kiworesultimport.test}/resources -Dpersistentcompetitors.clear=false -XX:+UseMembar -Digtimi.client.id=7fcdd217e0aa16090edb4ad55b09ec43b2021090e209541fc9b7003c2a2b70c6 -Digtimi.client.secret=aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a -Digtimi.client.redirecturi=http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc}"/>
|
||||
<stringAttribute key="pde.version" value="3.3"/>
|
||||
<booleanAttribute key="show_selected_only" value="false"/>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# Building, Deploying, Stopping and Starting Server Instances
|
||||
|
||||
[[_TOC_]]
|
||||
|
||||
## Running a Build
|
||||
|
||||
Builds are generally executed in either of the following two ways:
|
||||
|
||||
* Check out a branch of your liking from git and run <gitroot>/configuration/buildAndUpdateProduct.sh build (check the various options of this script by invoking it with no arguments). The build results including the p2 product repository are then located in the git workspace.
|
||||
|
||||
* Ensure that [Hudson](http://hudson.sapsailing.com) has a job for your branch; simply push the branch to the central git at sapsailing.com and let Hudson do the job. This is mostly good for knowing whether everything builds and tests ok. Only if you push the special "release" tag, Hudson will build a release and upload it to [releases.sapsailing.com](http://releases.sapsailing.com).
|
||||
|
||||
## Deploying Build Results
|
||||
|
||||
When the build has been run using the `buildAndUpdateProduct.sh` script, the build results in the git workspace can be deployed to a server environment under `~/servers/<servername>` using the command
|
||||
|
||||
<gitroot>/configuration/buildAndUpdateProduct.sh -s <servername> install
|
||||
|
||||
This will copy all necessary files, in particular the p2 product, to the server directory, including the start and stop scripts.
|
||||
|
||||
Again, check out the script's options for more and other possibilities including a remote deployment option and a hot deploy of individual bundles into a running server environment.
|
||||
|
||||
Deploying build results is generally also possible with a Hudson build, but it is not recommended because a user would need to log in to the Hudson server, know where which build workspace is located and then apply the deployment script there.
|
||||
|
||||
## Working with Releases
|
||||
|
||||
Particularly when starting an EC2 instance, it is helpful to be able to do that using a well-known release of the product. When an EC2 instance starts, it has a version of the product built into the image and its disk snapshots from which the instance got initialized. This, however, is usually not up to date. To refresh it, you could run a build from a specific git commit, or you could install a **release** previously assembled using the `release` option of the `buildAndUpdateProduct.sh` script as follows:
|
||||
|
||||
<gitroot>/configuration/buildAndUpdateProduct.sh build
|
||||
<gitroot>/configuration/buildAndUpdateProduct.sh -w trac@sapsailing.com -n <release-name> release
|
||||
|
||||
This will ask you for a comment about the release which goes into the release notes text file that accompanies the release. The build results are packed up into a .tar.gz file which is then uploaded to [releases.sapsailing.com](http://releases.sapsailing.com), using the name optionally provided using the -n parameter with the `release` action, or---as a default---the current timestamp for the release name.
|
||||
|
||||
A release can be downloaded and installed to a server by changing to the server's directory, e.g., `~/servers/server` and there executing the `updateEC2Instance.sh` script. Afterwards, starting the instance works as after a local build.
|
||||
|
||||
## Starting, Administrating and Stopping a Java Instance
|
||||
|
||||
The product runs as a Java instance consisting largely of an Equinox OSGi server that load and runs various OSGi bundles constituting the product and that contains an embedded Jetty web server. It connects to a database, may serve as a master for replication through a messaging server, may be launched as a replica of some other master instance, may receive wind data in _Expedition_ format on a specific port and can listen for telnet requests to administrate the OSGi server on a specific port. These and other properties are usually configured in a file called `env.sh` which has to be located in the server directory, e.g., in `~/servers/server`, next to the `start` script. When the `start` script is executed, it first sources the env.sh file which sets the various properties which are then passed to the actual Java process, usually in the form of system properties.
|
||||
|
||||
After successfully having started a Java instance, it can be administrated through a telnet connection. The port on which the OSGi console listens for incoming connections can be configured in the `env.sh` file. Usually it defaults to port `14888`. Therefore, a `telnet localhost 14888` connects you to the OSGi console where an `ss` command will show you all bundles loaded. Once logged on to the OSGi console, a `disconnect` command will disconnect the telnet session from the OSGi console.
|
||||
|
||||
An `exit` command will **terminate** the Java instance after a confirmation. This will obviously stop all services provided by the instance, including all static and dynamic contents served by its web server. You should only trigger the `exit` command if you really know what you are doing!
|
||||
|
||||
Stopping a running server has---for your convenience---been wrapped into the `stop` script usually located in the server's directory. Simply executing it will use a telnet connection to the server's OSGi port and trigger an `exit` command automatically. See above for the "know what you're doing" part...
|
||||
|
||||
## Automatic Java Instance Start-Up After Boot
|
||||
|
||||
When firing up an EC2 instance it can be convenient to not having to log on to have the EC2 instance run a Java instance automatically after it has completed its boot process. This is possible using so-called _user data_. The process of firing up an instance that either builds a certain git commit, installs and starts it after server boot or that downloads and installs a release and starts it is explained [here](http://wiki.sapsailing.com/wiki/amazon-ec2#HowTo).
|
||||
@@ -40,7 +40,7 @@ The Maven plug-in for the GWT compilation doesn't reliably perform a dependency
|
||||
|
||||
which basically does something like
|
||||
|
||||
`rm -rf com.sap.sailing.gwt.ui/com.sap.sailing.*; mvn -fae -P debug.without-proxy clean install 2>&1 | tee log`
|
||||
rm -rf com.sap.sailing.gwt.ui/com.sap.sailing.*; mvn -fae -P debug.without-proxy clean install 2>&1 | tee log
|
||||
|
||||
Inside the SAP VPN you may want to use a different profile which accounts for the proxies that have to be used:
|
||||
|
||||
@@ -52,6 +52,17 @@ When building on sapsailing.com you should stick with the buildAndUpdateProduct.
|
||||
|
||||
All these build lines also creates a log file with all error messages, just in case the screen buffer is not sufficient to hold all scrolling error messages.
|
||||
|
||||
## Automated Builds using Hudson
|
||||
|
||||
The project uses a Hudson build server installation that can be reached at [hudson.sapsailing.com](http://hudson.sapsailing.com). Please ask a project administrator for an account. This Hudson server builds all new commits pushed to the master branch, performs the JUnit tests and publishes the JUnit test results. New jobs for other branches can easily be created by copying from the SAPSailingAnalytics-master job and updating the git branch to be checked out for build. This way, you can create your own job for your own branch. Don't forget to set yourself as the e-mail recipient for failing builds.
|
||||
|
||||
As a special feature, release builds can automatically be performed and published to [releases.sapsailing.com](http:///releases.sapsailing.com) by pushing the tag named "release" to the version that you want to release. This can be done using the following series of git commands:
|
||||
|
||||
git tag -f release
|
||||
git push origin release:release
|
||||
|
||||
You can follow the build triggered by this [here](http://hudson.sapsailing.com/job/SAPSailingAnalytics-release/).
|
||||
|
||||
## Product, Features and Target Platform
|
||||
The result of the build process is a p2 repository with a product consisting of a number of features. The product configuration is provided by the file raceanalysis.product in the com.sap.sailing.feature.p2build project. In its dependencies it defines the features of which it is built, which currently are com.sap.sailing.feature and com.sap.sailing.feature.runtime, each described in an equal-named bundle. The feature specified by com.sap.sailing.feature lists the bundles we develop ourselves as part of the project. The com.sap.sailing.feature.runtime feature lists those 3rd-party bundles from the target platform which are required by the product.
|
||||
|
||||
|
||||
+3
-1
@@ -2,7 +2,9 @@
|
||||
|
||||
During our workshop of 2013-09-30, three major areas of development were identified as candidates for focus topics for the next few months. These are:
|
||||
|
||||
* [[General quality improvement, including major UI renovation and a mobile version of the SAP Sailing Analytics|wiki/planning/businesscases/QualityImprovements]]
|
||||
* [[Data quality improvement|wiki/planning/businesscases/DataQualityImprovement]]
|
||||
|
||||
* [[UI-Renovation|wiki/planning/businesscases/UIRenovation]]
|
||||
|
||||
* [[ORC / IRC support|wiki/planning/businesscases/ORC]]
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
Author: Simon Pamies
|
||||
|
||||
## Rationale
|
||||
SAP is getting involved in more and more sponsorship agreements in the sports sector and for each of these pushes to support the partner with technology. SAP puts a strong focus on providing analytics that, not only help people understand the sport and making it visible, but also thrives to help partners run their sport better.
|
||||
SAP is getting involved in more and more sponsorship agreements in the sports sector and for each of these pushes to support the partner with technology. SAP puts a strong focus on providing analytics that not only help people understand the sport and making it visible, but also thrives to help partners run their sport better.
|
||||
|
||||
These analytics, in most cases, rely on external data providers that feed data into a system SAP develops and maintains. These data sources are combined into a stream, that is then enriched with analytics specific to the sport and output either to a database or directly fed into a user interface.
|
||||
These analytics, in most cases, rely on external data providers that feed data into a system SAP develops and maintains. Over time or even at once, different data providers may need to be used. The data sources need to be adapted to fit a reasonable model of the domain based on which they can be enriched with analytics specific to the sport and output either to a database or directly fed into a user interface.
|
||||
|
||||
In most cases, there is an important distinction between live and non-live mode. During an event data is flowing into the SAP analytics and needs to be processed in realtime. In non-live mode data processing is not time-critical. As these partnerships aim to provide a live experience the main focus of all projects is to analyze data in realtime but to also provide historical data.
|
||||
|
||||
@@ -17,7 +17,7 @@ Provide developers, aiming to implement an analytical solution that analyzes dat
|
||||
|
||||
* Flexible Management of Code-Parts (OSGi)
|
||||
* Fast and flexible embedded Web-Server
|
||||
* Builtin support for release and deployment
|
||||
* Builtin life-cycle support (build, test, release and deploy)
|
||||
* Scaling
|
||||
* Replication (horizontal)
|
||||
* EC2 Integration (vertical)
|
||||
@@ -27,15 +27,16 @@ Provide developers, aiming to implement an analytical solution that analyzes dat
|
||||
* Connectors to external data providers
|
||||
* Unit- and Integration-Tests
|
||||
* In-Memory Analytics and Caching
|
||||
* Proven concurrency management utilizing multi-core architectures efficiently
|
||||
* Space efficient data structures
|
||||
* Eclipse Integration
|
||||
|
||||
## Description
|
||||
All the before mentioned features are available in bundles that are part of the Sailing Analytics project. It works very well for all needs related to this specific sport and the data providers known there. The software is grouped into so called bundles that are, in theory, independent from each other and provide a certain part of the logic (e.g. replication).
|
||||
All the features mentioned above are available in bundles that are part of the Sailing Analytics project. It works very well for all needs related to this specific sport and the data providers known there. The software is grouped into so called bundles that are, in theory, independent from each other and provide a certain part of the logic (e.g. replication).
|
||||
|
||||
Looking deeper into the current stack, most of the bundles contain code specific for the domain of sailing and in addition to that are strongly dependent from each other. The major work and cost driver of this project will be to extract and rewrite components that has been identified to become part of the shared code base.
|
||||
Looking deeper into the current stack, most of the bundles contain code specific for the domain of sailing and in addition to that are strongly dependent from each other. The major work and cost driver of this project will be to extract and rewrite components that have been identified to become part of the shared code base.
|
||||
|
||||
Once such a shared code base is in place and documented other developers could easily built on it and focus on implementing code related to their domain instead of having to re-invent things that already exist. They would then also be able to get support from other developers working with the same framework.
|
||||
Once such a shared code base is in place and documented, other developers could easily build on it and focus on implementing code related to their domain instead of having to re-invent things that already exist. They would then also be able to get support from other developers working with the same framework. Particularly tricky aspects such as concurrency management with locking and caching then will not have to be solved several times.
|
||||
|
||||
Such a project could also be described as a first step towards a productization process as the outcome is, in terms of documentation and cleanliness of code and architecture, the foundation of something that can be shared publicly.
|
||||
|
||||
@@ -49,10 +50,12 @@ This project will leverage many synergies at least between all developers workin
|
||||
|
||||
There are no risks that could make this project fail, as the steps and the implications are known. The biggest risk is to underestimate the time needed to extract all the components and to provide users with a good documentation. But this risk is mitigated by the fact that other projects can make use of it at an early stage even if not everything is yet extracted and documented.
|
||||
|
||||
Another risk is a potential slow-down of the activities around the Sailing Analytics because such a project only makes sense if the Sailing Analytics leverage the extracted code base as well.
|
||||
|
||||
## Prototype
|
||||
|
||||
There is currently no prototype.
|
||||
|
||||
## Estimation
|
||||
|
||||
It will take 2-4 weeks to come to a point where other sponsorship projects could start being build on top of it. It will take 3-6 weeks on top of the initial work amount to come to a version that is well documented and can be shared with developers that have no knowledge about the underlying details.
|
||||
It will take 2-4 weeks to come to a point where other sponsorship projects could start being built on top of it. It will take 3-6 weeks on top of the initial work amount to come to a version that is well documented and can be shared with developers that have no knowledge about the underlying details.
|
||||
@@ -0,0 +1,29 @@
|
||||
# RaceCommittee App User Guide (Administrator)
|
||||
|
||||
This user guide will help you to configure all your RaceCommittee App devices. The guide is targeted at a multi-device, multi-course-area, multi-race-officer setting. So feel free to omit steps whenever you feel that you don't have to use every feature for your current regatta. It is reasonable to stick to the presented order of steps.
|
||||
|
||||
## Preparation of devices
|
||||
|
||||
The RaceCommittee App supports remote configuration via the AdminConsole interface on the server. Open up the AdminConsole and select the tab **Device Configuration**. Click **Add Configuration**.
|
||||
|
||||
<img src="/wiki/images/rcapp/admin_config_step_1.png" />
|
||||
|
||||
First you need to enter a device identifier. It is a good idea to label the device with the this identifier, to enable the identification of the device by an untrained user.
|
||||
|
||||
<img src="/wiki/images/rcapp/admin_config_step_2.png" />
|
||||
|
||||
You need to assign this identifier to the target device. Click on **QR-Sync**, recheck the current identifier and ensure that the Server URL is the correct server the device should be talking to. When done with your modifications hit **Generate**.
|
||||
|
||||
<img src="/wiki/images/rcapp/admin_config_step_3.png" />
|
||||
|
||||
Fire up the RaceCommittee app on the target device. Go to the **settings view** by clicking on the options overflow button (top right corner) or your device's menu button. Select the preference category **General** and click on **Synchronize connection settings**. Scan the barcode shown in the AdminConsole (if you don't have a barcode app installed the RaceCommittee App should redirect you to the Google Play Store offering an app). The **Device Identifier** and **Webservice URL** preferences should reflect the changes made.
|
||||
|
||||
<img src="/wiki/images/rcapp/admin_config_step_4.png" />
|
||||
|
||||
Back in the AdminConsole configure your device (see next step). When done click **Save + Clone** to use the current configuration as a template for the next device.
|
||||
|
||||
Repeat until you've configured all your devices.
|
||||
|
||||
## Configuration of devices
|
||||
|
||||
There are several preferences you can configure to control the overall functionality of the RaceCommittee App. All these options can be configured either via remote configuration (see above) or on the device itself. The remote configuration is fetched on selecting an event in the event selection screen. If there is a configuration stored on the server for the target device, it will overwrite the corresponding options in the device's local configuration.
|
||||
@@ -9,6 +9,14 @@ A Feature List collected in September 2013 at the Testevent in Santander can be
|
||||
[Feature List RaceCommittee App](/doc/RaceCommittee Feature List.xlsx) (RaceCommittee Feature List, Sep. 2013, Julian Gimbel)
|
||||
The Feedback a User gave to the Application and the Feedback the user gave to the Applikation and Hardware of SwissTiming can be seen here:
|
||||
[Feature List RaceCommittee App](/doc/Swiss Timing Race Committee App.docx) (Swiss Timing App and Feedback for SAP RaceCommittee App, Sep. 2013, Julian Gimbel)
|
||||
|
||||
## User Guide
|
||||
|
||||
Have a look at the following user guides to get an idea how to work with the RaceCommittee App.
|
||||
|
||||
* [[RaceCommittee App as an administrator|wiki/racecommittee-app-administrator]]
|
||||
* [[RaceCommittee App as a race officer|wiki/racecommittee-app-user]]
|
||||
|
||||
## Course Designer
|
||||
|
||||
## Etc
|
||||
Reference in new issue
Block a user