Merge branch 'permission-vertical' into bug4942

This commit is contained in:
Benjamin Barth committed 2019-04-18 10:49:20 +02:00
commit 39417838bb
132 files changed
+3364 -1390

No files matched your search

@@ -41,6 +41,15 @@ public interface CompetitorDTO extends Named, WithID, SecuredDTO, Serializable,
* of the competitor's {@link #getName() name} unless it's empty in which case an empty string is returned.
*/
String getShortInfo();
/**
* Returns a derived short information about a competitor depending on the information available.
* Depending on the given preferSailId we prefer either {@link #getShortName() short name} or the sail ID from {@link CompetitorWithBoatDTO#getBoat() boat}
* if available. If both values aren't available, the boat name is used if a boat is available. If all these
* attributes have no value, a three-letter acronym is constructed from the name by using the first two and the last letter
* of the competitor's {@link #getName() name} unless it's empty in which case an empty string is returned.
*/
String getShortInfo(boolean preferSailId);
Color getColor();
@@ -160,6 +160,11 @@ public class CompetitorDTOImpl extends NamedSecuredObjectDTO implements Competit
@Override
public String getShortInfo() {
return getShortInfo(false);
}
@Override
public String getShortInfo(boolean preferSailId) {
final String result;
if (getShortName() != null && !getShortName().trim().isEmpty()) {
result = getShortName();
@@ -169,9 +174,9 @@ public class CompetitorDTOImpl extends NamedSecuredObjectDTO implements Competit
result = null;
} else {
result = (trimmedName.length()>0?""+trimmedName.charAt(0):"")
+ (trimmedName.length()>1?trimmedName.charAt(1):"")
+ (trimmedName.length()>0?trimmedName.charAt(trimmedName.length()-1):"");
+ (trimmedName.length()>1?trimmedName.charAt(1):"")
+ (trimmedName.length()>0?trimmedName.charAt(trimmedName.length()-1):"");
}
}
return result;
@@ -59,11 +59,13 @@ public class CompetitorWithBoatDTOImpl extends CompetitorDTOImpl implements Comp
public CompetitorWithBoatDTO getCompetitorFromPrevious(LeaderboardDTO previousVersion) {
return this;
}
@Override
public String getShortInfo() {
public String getShortInfo(boolean preferSailId) {
final String result;
if (getShortName() != null && !getShortName().trim().isEmpty()) {
if (preferSailId && getBoat() != null && getBoat().getSailId() != null) {
result = getBoat().getSailId();
} else if (getShortName() != null && !getShortName().trim().isEmpty()) {
result = getShortName();
} else if (getBoat() != null) {
result = getBoat().getSailId() != null ? getBoat().getSailId() : getBoat().getName();
@@ -59,6 +59,12 @@ public class PreviousCompetitorDTOImpl extends NamedSecuredObjectDTO implements
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorWithBoatDTOImpl.class.getName()+" after deserialization");
}
@Override
public String getShortInfo(boolean preferSailId) {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorWithBoatDTOImpl.class.getName()+" after deserialization");
}
@Override
public String getTwoLetterIsoCountryCode() {
@@ -3,5 +3,6 @@
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="resources"/>
<classpathentry kind="output" path="bin"/>
</classpath>
@@ -20,7 +20,12 @@ Require-Bundle: org.json.simple;bundle-version="1.1.0",
com.sap.sse.security,
com.sun.xml.bind.jaxb-impl;bundle-version="2.3.0",
com.sap.sse.security.common,
com.sap.sailing.domain.igtimiadapter
com.sap.sailing.domain.igtimiadapter,
com.sap.sse.security,
com.sap.sse.security.common,
com.sap.sse.security.interface,
org.apache.shiro.core,
org.apache.shiro.web
Import-Package: com.sap.sse.security.shared,
javax.ws.rs;version="1.1.1",
javax.ws.rs.core;version="1.1.1",
@@ -15,6 +15,32 @@
<param-value>false</param-value>
</init-param>
</servlet>
<context-param>
<param-name>shiroEnvironmentClass</param-name>
<param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value>
</context-param>
<!-- Apache Shiro -->
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<!-- Make sure any request you want accessible to Shiro is filtered. "/*"
catches all requests. Usually this filter mapping is defined first (before all
others) to ensure that Shiro works in subsequent filters in the filter chain: -->
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<servlet>
<servlet-name>Igtimi Oauth Callback REST API</servlet-name>
@@ -1,4 +1,5 @@
source.. = src/
source.. = src/,\
resources/
output.. = bin/
bin.includes = META-INF/,\
.,\
@@ -0,0 +1,45 @@
[main]
credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
credentialsMatcher.storedCredentialsHexEncoded = false
credentialsMatcher.hashIterations = 1024
# Realm configuration:
# --------------------
upRealm = com.sap.sse.security.UsernamePasswordRealm
upRealm.credentialsMatcher = $credentialsMatcher
oauthRealm = com.sap.sse.security.OAuthRealm
bearerTokenRealm = com.sap.sse.security.BearerTokenRealm
# Putting it all together:
sessionManager = com.sap.sse.security.SecurityWebSessionManager
securityManager.sessionManager = $sessionManager
sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
securityManager.sessionManager.sessionDAO = $sessionDAO
subjectDAO = com.sap.sse.security.NoSessionStorageSessionDAO
securityManager.subjectDAO = $subjectDAO
# 1y timeout
securityManager.sessionManager.globalSessionTimeout = 31536000000
cacheManager = com.sap.sse.security.SessionCacheManager
securityManager.cacheManager = $cacheManager
# Support for anonymous user permissions
webSubjectFactoryWithAnonymousPrincipalSupport=com.sap.sse.security.WebSubjectFactoryWithAnonymousPrincipalSupport
securityManager.subjectFactory=$webSubjectFactoryWithAnonymousPrincipalSupport
# Authentication Filter Configurations
# ------------------------------------
# This authentication filter accepts a bearer access token in the HTTP
# Authorization header field, as in
# Authorization: Bearer 1029741026501365024376093245
# If no such bearer token is provided, the filter falls back to basic HTTP
# authentication ("Authentication: Basic ...") and then regular form-based
# authentication with POST parameters "username" and "password".
bearerToken = com.sap.sse.security.BearerTokenOrBasicOrFormAuthenticationFilter
# Specifying filter chains for URL patterns
[urls]
/authorizationcallback = bearerToken
@@ -10,6 +10,7 @@ import org.osgi.util.tracker.ServiceTracker;
import com.sap.sailing.domain.igtimiadapter.Client;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
import com.sap.sse.security.SecurityService;
import com.sap.sse.util.ServiceTrackerFactory;
/**
@@ -25,6 +26,7 @@ import com.sap.sse.util.ServiceTrackerFactory;
public class Activator implements BundleActivator {
private static Activator INSTANCE;
private ServiceTracker<IgtimiConnectionFactory, IgtimiConnectionFactory> igtimiConnectionFactoryTracker;
private ServiceTracker<SecurityService, SecurityService> securityServiceTracker;
public Activator() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
}
@@ -35,6 +37,8 @@ public class Activator implements BundleActivator {
igtimiConnectionFactoryTracker = ServiceTrackerFactory
.createAndOpen(context, IgtimiConnectionFactory.class);
securityServiceTracker = ServiceTrackerFactory.createAndOpen(context, SecurityService.class);
}
public static Activator getInstance() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
@@ -48,9 +52,15 @@ public class Activator implements BundleActivator {
return igtimiConnectionFactoryTracker.getService();
}
public SecurityService getSecurityService() {
return securityServiceTracker.getService();
}
@Override
public void stop(BundleContext context) throws Exception {
igtimiConnectionFactoryTracker.close();
igtimiConnectionFactoryTracker = null;
securityServiceTracker.close();
securityServiceTracker = null;
}
}
@@ -21,6 +21,8 @@ import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.igtimiadapter.gateway.impl.Activator;
import com.sap.sailing.domain.igtimiadapter.impl.IgtimiConnectionFactoryImpl;
import com.sap.sse.security.SecurityService;
import com.sap.sse.security.shared.impl.User;
@Path(AuthorizationCallback.V1)
public class AuthorizationCallback {
@@ -57,8 +59,18 @@ public class AuthorizationCallback {
} else {
if (code != null) {
try {
connectionFactory.obtainAccessTokenFromAuthorizationCode(code);
result = Response.ok().entity("SAP Sailing Analytics authorized successfully to access user's tracks").build();
final SecurityService securityService = Activator.getInstance().getSecurityService();
final User currentUser = securityService.getCurrentUser();
if (currentUser != null) {
connectionFactory.obtainAccessTokenFromAuthorizationCode(currentUser.getName(), code);
result = Response.ok()
.entity("SAP Sailing Analytics authorized successfully to access user's tracks")
.build();
} else {
result = Response.status(Status.UNAUTHORIZED).entity(
"SAP Sailing Analytics could not authorize access user's tracks: Current user not found.")
.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);
@@ -2,6 +2,6 @@ package com.sap.sailing.domain.igtimiadapter.persistence;
public interface DomainObjectFactory {
Iterable<String> getAccessTokens();
Iterable<TokenAndCreator> getAccessTokens();
}
@@ -2,8 +2,10 @@ package com.sap.sailing.domain.igtimiadapter.persistence;
public interface MongoObjectFactory {
void storeAccessToken(String accessToken);
void storeAccessToken(String creatorName, String accessToken);
void removeAccessToken(String accessToken);
void removeAccessToken(String creatorName, String accessToken);
void clear();
}
@@ -0,0 +1,21 @@
package com.sap.sailing.domain.igtimiadapter.persistence;
public class TokenAndCreator {
private final String creatorName;
private final String accessToken;
public TokenAndCreator(String creatorName, String accessToken) {
this.creatorName = creatorName;
this.accessToken = accessToken;
}
public String getCreatorName() {
return creatorName;
}
public String getAccessToken() {
return accessToken;
}
}
@@ -8,6 +8,7 @@ import org.bson.Document;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.sap.sailing.domain.igtimiadapter.persistence.DomainObjectFactory;
import com.sap.sailing.domain.igtimiadapter.persistence.TokenAndCreator;
public class DomainObjectFactoryImpl implements DomainObjectFactory {
private final MongoDatabase db;
@@ -17,12 +18,26 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
}
@Override
public Iterable<String> getAccessTokens() {
List<String> result = new ArrayList<>();
public Iterable<TokenAndCreator> getAccessTokens() {
List<TokenAndCreator> result = new ArrayList<>();
final MongoCollection<org.bson.Document> accessTokenCollection = db.getCollection(CollectionNames.IGTIMI_ACCESS_TOKENS.name());
accessTokenCollection.createIndex(new Document(FieldNames.IGTIMI_ACCESS_TOKENS_ACCESS_TOKEN.name(), 1));
for (Object o : accessTokenCollection.find()) {
result.add((String) ((Document) o).get(FieldNames.IGTIMI_ACCESS_TOKENS_ACCESS_TOKEN.name()));
final String accessToken = (String) ((Document) o).get(FieldNames.IGTIMI_ACCESS_TOKENS_ACCESS_TOKEN.name());
String creatorName= (String) ((Document) o).get(FieldNames.CREATOR_NAME.name());
final boolean needsUpdate = (creatorName == null);
if (needsUpdate) {
// No creator is set yet -> existing token are assumed to belong to the admin
creatorName = "admin";
// recreating the token on the DB because the composite key changed
MongoObjectFactoryImpl mongoObjectFactory = new MongoObjectFactoryImpl(db);
mongoObjectFactory.removeAccessToken(null, accessToken);
mongoObjectFactory.storeAccessToken(creatorName, accessToken);
}
result.add(new TokenAndCreator(creatorName, accessToken));
}
return result;
}
@@ -1,5 +1,6 @@
package com.sap.sailing.domain.igtimiadapter.persistence.impl;
public enum FieldNames {
IGTIMI_ACCESS_TOKENS_ACCESS_TOKEN;
IGTIMI_ACCESS_TOKENS_ACCESS_TOKEN,
CREATOR_NAME;
}
@@ -13,21 +13,28 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
public MongoObjectFactoryImpl(MongoDatabase db) {
this.db = db;
}
@Override
public void clear() {
db.getCollection(CollectionNames.IGTIMI_ACCESS_TOKENS.name()).withWriteConcern(WriteConcern.ACKNOWLEDGED)
.drop();
}
@Override
public void storeAccessToken(String accessToken) {
final Document basicDBObject = getAccessTokenDBQuery(accessToken);
public void storeAccessToken(String creatorName, String accessToken) {
final Document basicDBObject = getAccessTokenDBQuery(creatorName, accessToken);
db.getCollection(CollectionNames.IGTIMI_ACCESS_TOKENS.name()).withWriteConcern(WriteConcern.ACKNOWLEDGED).replaceOne(basicDBObject, basicDBObject, new UpdateOptions().upsert(true));
}
private Document getAccessTokenDBQuery(String accessToken) {
private Document getAccessTokenDBQuery(String creatorName, String accessToken) {
final Document basicDBObject = new Document(FieldNames.IGTIMI_ACCESS_TOKENS_ACCESS_TOKEN.name(), accessToken);
basicDBObject.put(FieldNames.CREATOR_NAME.name(), creatorName);
return basicDBObject;
}
@Override
public void removeAccessToken(String accessToken) {
final Document basicDBObject = getAccessTokenDBQuery(accessToken);
public void removeAccessToken(String creatorName, String accessToken) {
final Document basicDBObject = getAccessTokenDBQuery(creatorName, accessToken);
db.getCollection(CollectionNames.IGTIMI_ACCESS_TOKENS.name()).withWriteConcern(WriteConcern.ACKNOWLEDGED).deleteOne(basicDBObject);
}
}
@@ -10,4 +10,7 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Require-Bundle: org.junit;bundle-version="4.8.2",
com.sap.sailing.domain.common,
com.sap.sse.mongodb,
com.sap.sse.common
com.sap.sse.common,
com.sap.sse.security.testsupport,
org.mockito.mockito-core;bundle-version="1.10.14",
org.hamcrest;bundle-version="1.1.0"
@@ -11,6 +11,8 @@ import javax.xml.parsers.ParserConfigurationException;
import org.apache.http.client.ClientProtocolException;
import org.json.simple.parser.ParseException;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
@@ -25,11 +27,22 @@ import com.sap.sailing.domain.igtimiadapter.impl.IgtimiConnectionFactoryImpl;
import com.sap.sailing.domain.igtimiadapter.persistence.PersistenceFactory;
import com.sap.sse.mongodb.MongoDBConfiguration;
import com.sap.sse.mongodb.MongoDBService;
import com.sap.sse.security.testsupport.SecurityServiceMockFactory;
public class SignInTest {
private static final Logger logger = Logger.getLogger(SignInTest.class.getName());
@Rule public Timeout AbstractTracTracLiveTestTimeout = Timeout.millis(2 * 60 * 1000);
@Before
public void setUp() throws ClientProtocolException, IOException, org.json.simple.parser.ParseException {
Activator.getInstance().setSecurityService(SecurityServiceMockFactory.mockSecurityService());
}
@After
public void tearDown() throws Exception {
Activator.getInstance().setSecurityService(null);
}
@Test
public void testSimpleSignIn() throws ClientProtocolException, IOException, IllegalStateException,
@@ -45,7 +58,7 @@ public class SignInTest {
final String code = igtimiConnectionFactory.authorizeAndReturnAuthorizedCode("axel.uhl@gmx.de", "123456");
logger.info("Igtimi OAuth code is "+code);
assertNotNull(code);
Account account = igtimiConnectionFactory.obtainAccessTokenFromAuthorizationCode(code);
Account account = igtimiConnectionFactory.obtainAccessTokenFromAuthorizationCode("admin", code);
assertEquals("axel.uhl@gmx.de", account.getUser().getEmail());
}
@@ -76,7 +89,7 @@ public class SignInTest {
MongoDBService mongoTestService = mongoTestConfig.getService();
final IgtimiConnectionFactory igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService),
PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoTestService));
final Account account = igtimiConnectionFactory.createAccountToAccessUserData("axel.uhl@gmx.de", "123456");
final Account account = igtimiConnectionFactory.createAccountToAccessUserData("admin", "axel.uhl@gmx.de", "123456");
assertNotNull(account);
logger.info("Igtimi account is "+account);
}
@@ -84,7 +97,7 @@ public class SignInTest {
@Test
public void testAddToken() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory();
Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("de2d6531236200f7c9fb69a0463ffe8d6b13f62bd7aad8de98c22862e4928e8a");
Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("admin", "de2d6531236200f7c9fb69a0463ffe8d6b13f62bd7aad8de98c22862e4928e8a");
assertEquals("axel.uhl@gmx.de", account.getUser().getEmail());
assertSame(account, connectionFactory.getExistingAccountByEmail("axel.uhl@gmx.de"));
}
@@ -6,10 +6,10 @@ import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import com.sap.sse.common.Util;
import com.sap.sse.mongodb.MongoDBConfiguration;
public class IgtimiPersistenceTest {
private static final String CREATOR = "admin";
private MongoObjectFactory mongoObjectFactory;
private DomainObjectFactory domainObjectFactory;
@@ -22,11 +22,22 @@ public class IgtimiPersistenceTest {
@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));
mongoObjectFactory.removeAccessToken(CREATOR, accessToken);
assertFalse(isAccessTokenContained(domainObjectFactory.getAccessTokens(), accessToken));
mongoObjectFactory.storeAccessToken(CREATOR, accessToken);
assertTrue(isAccessTokenContained(domainObjectFactory.getAccessTokens(), accessToken));
mongoObjectFactory.removeAccessToken(CREATOR, accessToken);
assertFalse(isAccessTokenContained(domainObjectFactory.getAccessTokens(), accessToken));
}
private boolean isAccessTokenContained(Iterable<TokenAndCreator> availableToken, String accessToken) {
boolean result = false;
for (TokenAndCreator token : availableToken) {
if (accessToken.equals(token.getAccessToken())) {
result = true;
break;
}
}
return result;
}
}
@@ -1,8 +1,6 @@
package com.sap.sailing.domain.igtimiadapter.test;
import java.io.IOException;
import org.apache.http.client.ClientProtocolException;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.Timeout;
@@ -11,6 +9,7 @@ import com.sap.sailing.domain.igtimiadapter.Account;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
import com.sap.sailing.domain.igtimiadapter.impl.Activator;
import com.sap.sse.security.testsupport.SecurityServiceMockFactory;
public class AbstractTestWithIgtimiConnection {
protected IgtimiConnection connection;
@@ -19,11 +18,18 @@ public class AbstractTestWithIgtimiConnection {
@Rule public Timeout AbstractTracTracLiveTestTimeout = Timeout.millis(2 * 60 * 1000);
@Before
public void setUp() throws ClientProtocolException, IOException, org.json.simple.parser.ParseException {
public void setUp() throws Exception {
Activator.getInstance().setSecurityService(SecurityServiceMockFactory.mockSecurityService());
final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory();
Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("9fded995cf21c8ed91ddaec13b220e8d5e44c65808d22ec2b1b7c32261121f26");
Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("admin", "9fded995cf21c8ed91ddaec13b220e8d5e44c65808d22ec2b1b7c32261121f26");
connection = connectionFactory.connect(account);
}
@After
public void tearDown() throws Exception {
Activator.getInstance().setSecurityService(null);
}
}
@@ -4,8 +4,6 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.HashSet;
import java.util.Locale;
@@ -13,7 +11,6 @@ import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import org.apache.http.client.ClientProtocolException;
import org.junit.Test;
import com.sap.sailing.domain.common.Wind;
@@ -41,8 +38,7 @@ public class IgtimiFixTrackTest extends AbstractTestWithIgtimiConnection {
private static final Logger logger = Logger.getLogger(IgtimiFixTrackTest.class.getName());
@Test
public void testFetchFixesIntoTracks() throws ParseException, ClientProtocolException, IllegalStateException, IOException, org.json.simple.parser.ParseException {
setUp();
public void testFetchFixesIntoTracks() throws Exception {
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.GERMAN);
TimePoint start = new MillisecondsTimePoint(dateFormat.parse("2013-11-09T07:00:00Z"));
TimePoint end = new MillisecondsTimePoint(dateFormat.parse("2013-11-09T07:10:00Z"));
@@ -19,6 +19,7 @@ import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.http.client.ClientProtocolException;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
@@ -28,6 +29,8 @@ 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.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
@@ -39,12 +42,14 @@ import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
import com.sap.sailing.domain.igtimiadapter.LiveDataConnection;
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
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.sse.common.Util;
import com.sap.sse.mongodb.MongoDBConfiguration;
import com.sap.sse.mongodb.MongoDBService;
import com.sap.sse.security.testsupport.SecurityServiceMockFactory;
public class WebSocketTest {
private static final Logger logger = Logger.getLogger(WebSocketTest.class.getName());
@@ -116,6 +121,16 @@ public class WebSocketTest {
}
}
@Before
public void setUp() throws ClientProtocolException, IOException, org.json.simple.parser.ParseException {
Activator.getInstance().setSecurityService(SecurityServiceMockFactory.mockSecurityService());
}
@After
public void tearDown() throws Exception {
Activator.getInstance().setSecurityService(null);
}
@Test
public void simpleWebSocketEchoTest() throws Exception {
String destUri = "ws://echo.websocket.org"; // wss currently doesn't seem to work with Jetty 9.0.4 WebSocket implementation
@@ -162,7 +177,7 @@ public class WebSocketTest {
PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoTestService));
// the following is an access token for an account allowing axel.uhl@gmx.de to access
// the data from baur@stg-academy.org, particularly containing the Berlin test data
Account account = igtimiConnectionFactory.registerAccountForWhichClientIsAuthorized("9fded995cf21c8ed91ddaec13b220e8d5e44c65808d22ec2b1b7c32261121f26");
Account account = igtimiConnectionFactory.registerAccountForWhichClientIsAuthorized("admin", "9fded995cf21c8ed91ddaec13b220e8d5e44c65808d22ec2b1b7c32261121f26");
IgtimiConnection conn = igtimiConnectionFactory.connect(account);
LiveDataConnection liveDataConnection = conn.getOrCreateLiveConnection(Collections.singleton("GA-EN-AAEJ"));
LiveDataConnection redundantSecondSharedConnection = conn.getOrCreateLiveConnection(Collections.singleton("GA-EN-AAEJ"));
@@ -11,6 +11,8 @@ import com.sap.sse.security.shared.WithQualifiedObjectIdentifier;
*/
public interface Account extends WithQualifiedObjectIdentifier {
User getUser();
String getCreatorName();
@Override
default HasPermissions getType() {
@@ -22,16 +24,20 @@ public interface Account extends WithQualifiedObjectIdentifier {
return getUser().getFirstName() + " " + getUser().getSurname();
}
@Override
default QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
}
default TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier() {
return getTypeRelativeObjectIdentifier(getUser().getEmail());
return getCreatorName() == null ? new TypeRelativeObjectIdentifier(getUser().getEmail())
: new TypeRelativeObjectIdentifier(getUser().getEmail(), getCreatorName());
}
static TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier(String email) {
return new TypeRelativeObjectIdentifier(email);
static TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier(String email, String creatorName) {
return new TypeRelativeObjectIdentifier(email, creatorName);
}
}
@@ -43,7 +43,7 @@ public interface IgtimiConnectionFactory {
* which this factory belongs.
* @return
*/
Account registerAccountForWhichClientIsAuthorized(String accessToken) throws ClientProtocolException,
Account registerAccountForWhichClientIsAuthorized(String creatorName, String accessToken) throws ClientProtocolException,
IllegalStateException, IOException, ParseException;
/**
@@ -59,7 +59,7 @@ public interface IgtimiConnectionFactory {
* @return the account with which a caller can then {@link #connect obtain a connection} for the data that the user
* identified by <code>userEmail</code> and <code>userPassword</code> shares with our client.
*/
Account createAccountToAccessUserData(String userEmail, String userPassword) throws ClientProtocolException,
Account createAccountToAccessUserData(String creatorName, String userEmail, String userPassword) throws ClientProtocolException,
IOException, IllegalStateException, ParserConfigurationException, SAXException, ClassNotFoundException,
InstantiationException, IllegalAccessException, ClassCastException, ParseException;
@@ -6,9 +6,11 @@ import com.sap.sailing.domain.igtimiadapter.User;
public class AccountImpl implements Account {
private static final long serialVersionUID = 1L;
private final User user;
private final String creatorName;
public AccountImpl(User user) {
public AccountImpl(String creatorName, User user) {
super();
this.creatorName = creatorName;
this.user = user;
}
@@ -16,6 +18,11 @@ public class AccountImpl implements Account {
public User getUser() {
return user;
}
@Override
public String getCreatorName() {
return creatorName;
}
@Override
public String toString() {
@@ -24,6 +24,7 @@ import com.sap.sailing.domain.igtimiadapter.persistence.MongoObjectFactory;
import com.sap.sailing.domain.igtimiadapter.persistence.PersistenceFactory;
import com.sap.sailing.domain.tracking.WindTrackerFactory;
import com.sap.sse.security.SecurityService;
import com.sap.sse.util.ClearStateTestSupport;
import com.sap.sse.util.ServiceTrackerFactory;
import com.sap.sse.util.impl.ThreadFactoryWithPriority;
@@ -54,7 +55,9 @@ public class Activator implements BundleActivator {
private static final String CLIENT_REDIRECT_PORT_PROPERTY_NAME = "igtimi.client.redirect.port";
private final Future<IgtimiConnectionFactoryImpl> connectionFactory;
private final Future<IgtimiWindTrackerFactory> windTrackerFactory;
private ServiceTracker<SecurityService, SecurityService> securityServiceServiceTracker;
private final ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactoryWithPriority(Thread.NORM_PRIORITY, /* daemon */ true));
private SecurityService securityServiceTest;
public Activator() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
logger.info(getClass().getName()+" constructor");
@@ -82,6 +85,11 @@ public class Activator implements BundleActivator {
});
}
/** Only used by tests. */
public void setSecurityService(SecurityService securityService) {
securityServiceTest = securityService;
}
@Override
public void start(final BundleContext context) throws Exception {
INSTANCE = this;
@@ -99,9 +107,8 @@ public class Activator implements BundleActivator {
}
});
securityServiceServiceTracker = ServiceTrackerFactory.createAndOpen(context, SecurityService.class);
new Thread(() -> {
final ServiceTracker<SecurityService, SecurityService> securityServiceServiceTracker = ServiceTrackerFactory
.createAndOpen(context, SecurityService.class);
try {
final SecurityService securityService = securityServiceServiceTracker.waitForService(0);
IgtimiConnectionFactoryImpl igtimiConnectionFactory = connectionFactory.get();
@@ -114,6 +121,12 @@ public class Activator implements BundleActivator {
}
}, getClass().getName() + " registering connectivity handler").start();
context.registerService(ClearStateTestSupport.class.getName(), new ClearStateTestSupport() {
@Override
public void clearState() throws Exception {
connectionFactory.get().clear();
}
}, null);
}
public static Activator getInstance() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
@@ -132,6 +145,10 @@ public class Activator implements BundleActivator {
}
}
public SecurityService getSecurityService() {
return securityServiceTest == null ? securityServiceServiceTracker.getService() : securityServiceTest;
}
public IgtimiWindTrackerFactory getWindTrackerFactory() {
try {
return windTrackerFactory.get();
@@ -143,5 +160,7 @@ public class Activator implements BundleActivator {
@Override
public void stop(BundleContext context) throws Exception {
securityServiceServiceTracker.close();
securityServiceServiceTracker = null;
}
}
@@ -43,6 +43,7 @@ import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sailing.domain.igtimiadapter.Account;
import com.sap.sailing.domain.igtimiadapter.Client;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
@@ -51,7 +52,9 @@ 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;
import com.sap.sailing.domain.igtimiadapter.persistence.TokenAndCreator;
import com.sap.sse.common.TimePoint;
import com.sap.sse.security.SecurityService;
import com.sap.sse.util.LaxRedirectStrategyForAllRedirectResponseCodes;
public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
@@ -69,32 +72,55 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
connectionsByAccount = new HashMap<>();
this.client = client;
this.mongoObjectFactory = mongoObjectFactory;
for (String accessToken : domainObjectFactory.getAccessTokens()) {
for (TokenAndCreator accessTokenWithCreator : domainObjectFactory.getAccessTokens()) {
try {
registerAccountForWhichClientIsAuthorized(accessToken);
storeIgtimiAccount(accessTokenWithCreator.getCreatorName(), accessTokenWithCreator.getAccessToken(),
getAccount(accessTokenWithCreator.getCreatorName(), accessTokenWithCreator.getAccessToken()));
} 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);
logger.log(Level.SEVERE, "Error registering Igtimi access token " + accessTokenWithCreator
+ "; probably the access token was revoked or expired.", e);
mongoObjectFactory.removeAccessToken(accessTokenWithCreator.getCreatorName(),
accessTokenWithCreator.getAccessToken());
}
}
}
public void clear() {
accessTokensByAccount.clear();
accountsByEmail.clear();
connectionsByAccount.clear();
mongoObjectFactory.clear();
}
@Override
public Account registerAccountForWhichClientIsAuthorized(String accessToken) throws ClientProtocolException, IllegalStateException, IOException, ParseException {
Account account = getAccount(accessToken);
public Account registerAccountForWhichClientIsAuthorized(String creatorName, String accessToken)
throws ClientProtocolException, IllegalStateException, IOException, ParseException {
Account account = getAccount(creatorName, accessToken);
return getSecurityService().setOwnershipCheckPermissionForObjectCreationAndRevertOnError(
SecuredDomainType.IGTIMI_ACCOUNT, account.getIdentifier().getTypeRelativeObjectIdentifier(),
account.getUser().getEmail(), () -> {
return storeIgtimiAccount(creatorName, accessToken, account);
});
}
private Account storeIgtimiAccount(String creatorName, String accessToken, Account account) {
accountsByEmail.put(account.getUser().getEmail(), account);
accessTokensByAccount.put(account, accessToken);
mongoObjectFactory.storeAccessToken(accessToken);
mongoObjectFactory.storeAccessToken(creatorName, accessToken);
return account;
}
private Account getAccount(String accessToken) throws ClientProtocolException, IOException, IllegalStateException, ParseException {
protected SecurityService getSecurityService() throws ClientProtocolException, IOException, ParseException {
return Activator.getInstance().getSecurityService();
}
private Account getAccount(String creatorName, String accessToken) throws ClientProtocolException, IOException, IllegalStateException, ParseException {
HttpClient client = new SystemDefaultHttpClient();
HttpGet getAccount = new HttpGet(getApiV1BaseUrl()+"account?access_token="+accessToken);
HttpResponse accountResponse = client.execute(getAccount);
JSONObject accountJson = ConnectivityUtils.getJsonFromResponse(accountResponse);
JSONObject userJson = (JSONObject) accountJson.get("user");
Account account = new AccountImpl(new UserDeserializer().createUserFromJson(userJson));
Account account = new AccountImpl(creatorName, new UserDeserializer().createUserFromJson(userJson));
return account;
}
@@ -348,7 +374,7 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
* @return the account encoding the application that is authorized for a user's account
* @throws RuntimeException in case there was an error while retrieving the token
*/
public Account obtainAccessTokenFromAuthorizationCode(String code) throws ClientProtocolException, IOException, IllegalStateException, ParseException {
public Account obtainAccessTokenFromAuthorizationCode(String creatorName, String code) throws ClientProtocolException, IOException, IllegalStateException, ParseException {
HttpClient client = new SystemDefaultHttpClient();
HttpPost post = new HttpPost(getOauthTokenUrl());
List<NameValuePair> urlParameters = new ArrayList<NameValuePair>();
@@ -365,7 +391,7 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
throw new RuntimeException(accessTokenJson.toString());
} else {
String accessToken = (String) accessTokenJson.get("access_token");
result = registerAccountForWhichClientIsAuthorized(accessToken);
result = registerAccountForWhichClientIsAuthorized(creatorName, accessToken);
}
return result;
}
@@ -390,13 +416,13 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
}
@Override
public Account createAccountToAccessUserData(String userEmail, String userPassword) throws ClientProtocolException,
public Account createAccountToAccessUserData(String creatorName, String userEmail, String userPassword) throws ClientProtocolException,
IOException, IllegalStateException, ParserConfigurationException, SAXException, ClassNotFoundException,
InstantiationException, IllegalAccessException, ClassCastException, ParseException {
final Account result;
String code = authorizeAndReturnAuthorizedCode(userEmail, userPassword);
if (code != null) {
result = obtainAccessTokenFromAuthorizationCode(code);
result = obtainAccessTokenFromAuthorizationCode(creatorName, code);
} else {
result = null;
}
@@ -526,7 +552,7 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
if (account != null) {
String accessToken = accessTokensByAccount.remove(account);
accountsByEmail.remove(account.getUser().getEmail());
mongoObjectFactory.removeAccessToken(accessToken);
mongoObjectFactory.removeAccessToken(account.getCreatorName(), accessToken);
}
}
@@ -1895,11 +1895,9 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
Document dbObject = (Document) object;
final Serializable competitorId = (Serializable) dbObject.get(FieldNames.COMPETITOR_ID.name());
String competitorDisplayName = (String) dbObject.get(FieldNames.COMPETITOR_DISPLAY_NAME.name());
// The Competitor name is a new field in the list. Therefore the name might be null for existing events. In
// this case a standard name is set.
if (competitorDisplayName == null) {
competitorDisplayName = "loaded competitor";
}
String competitorShortName = (String) dbObject.get(FieldNames.COMPETITOR_SHORT_NAME.name());
String competitorBoatName = (String) dbObject.get(FieldNames.COMPETITOR_BOAT_NAME.name());
String competitorBoatSailId = (String) dbObject.get(FieldNames.COMPETITOR_BOAT_SAIL_ID.name());
// At this point we do not retrieve the competitor object since at any point in time, especially after a
// server restart, the DomainFactory and its competitor
// cache might be empty. But at this time the race log is loaded from database, so the competitor would be
@@ -1925,8 +1923,9 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
} else {
mergeState = MergeState.valueOf(mergeStateAsString);
}
CompetitorResultImpl positionedCompetitor = new CompetitorResultImpl(
competitorId, competitorDisplayName, rank == null ? rankCounter : rank, maxPointsReason, score, finishingTime, comment, mergeState);
CompetitorResultImpl positionedCompetitor = new CompetitorResultImpl(competitorId, competitorDisplayName,
competitorShortName, competitorBoatName, competitorBoatSailId, rank == null ? rankCounter : rank,
maxPointsReason, score, finishingTime, comment, mergeState);
positionedCompetitors.add(positionedCompetitor);
rankCounter++;
}
@@ -76,7 +76,7 @@ public enum FieldNames {
// SwissTiming configuration parameters:
ST_CONFIG_NAME, ST_CONFIG_HOSTNAME, ST_CONFIG_PORT,
COMPETITOR_ID, COMPETITOR_DISPLAY_NAME,
COMPETITOR_ID, COMPETITOR_DISPLAY_NAME, COMPETITOR_SHORT_NAME, COMPETITOR_BOAT_NAME, COMPETITOR_BOAT_SAIL_ID,
// leaderboard:
LEADERBOARD_NAME, LEADERBOARD_DISPLAY_NAME, LEADERBOARD_SUPPRESSED_COMPETITORS, LEADERBOARD_SUPPRESSED_COMPETITOR_IDS,
@@ -1255,7 +1255,10 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
private Document storePositionedCompetitor(CompetitorResult competitorResult) {
Document result = new Document();
result.put(FieldNames.COMPETITOR_ID.name(), competitorResult.getCompetitorId());
result.put(FieldNames.COMPETITOR_DISPLAY_NAME.name(), competitorResult.getCompetitorDisplayName());
result.put(FieldNames.COMPETITOR_DISPLAY_NAME.name(), competitorResult.getName());
result.put(FieldNames.COMPETITOR_SHORT_NAME.name(), competitorResult.getShortName());
result.put(FieldNames.COMPETITOR_BOAT_NAME.name(), competitorResult.getBoatName());
result.put(FieldNames.COMPETITOR_BOAT_SAIL_ID.name(), competitorResult.getBoatSailId());
result.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name(), competitorResult.getMaxPointsReason() == null ? null : competitorResult.getMaxPointsReason().name());
result.put(FieldNames.LEADERBOARD_CORRECTED_SCORE.name(), competitorResult.getScore());
result.put(FieldNames.RACE_LOG_FINISHING_TIME_AS_MILLIS.name(), competitorResult.getFinishingTime() == null ? null : competitorResult.getFinishingTime().asMillis());
@@ -57,10 +57,10 @@ public class ConfirmedFinishPositioningListFinderTest extends
public void testMostRecent() {
RaceLogFinishPositioningConfirmedEvent event1 = createEvent(RaceLogFinishPositioningConfirmedEvent.class, 1);
RaceLogFinishPositioningConfirmedEvent event2 = createEvent(RaceLogFinishPositioningConfirmedEvent.class, 2);
final CompetitorResultImpl oldResultC1 = new CompetitorResultImpl("Comp1", "Comp 1", 1, MaxPointsReason.NONE, 1.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResultImpl oldResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.NONE, 2.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResultImpl oldResultC1 = new CompetitorResultImpl("Comp1", "Comp 1", "Comp 1 Short", "Comp 1 Boat", "Comp 1 Boat Id", 1, MaxPointsReason.NONE, 1.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResultImpl oldResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", "Comp 2 Short", "Comp 2 Boat", "Comp 2 Boat Id", 2, MaxPointsReason.NONE, 2.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResults olderResults = mockCompetitorResults(oldResultC1, oldResultC2);
final CompetitorResultImpl newResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResultImpl newResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", "Comp 3 Short", "Comp 3 Boat", "Comp 3 Boat Id", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResults newerResults = mockCompetitorResults(newResultC2);
when(event1.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(olderResults);
when(event2.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(newerResults);
@@ -75,16 +75,16 @@ public class ConfirmedFinishPositioningListFinderTest extends
RaceLogFinishPositioningConfirmedEvent event2 = createEvent(/* priority */ 1, RaceLogFinishPositioningConfirmedEvent.class, 2);
RaceLogFinishPositioningConfirmedEvent event3 = createEvent(/* priority */ 2, RaceLogFinishPositioningConfirmedEvent.class, 3);
RaceLogFinishPositioningConfirmedEvent event4 = createEvent(/* priority */ 1, RaceLogFinishPositioningConfirmedEvent.class, 4);
final CompetitorResultImpl e1ResultC1 = new CompetitorResultImpl("Comp1", "Comp 1", 1, MaxPointsReason.DNC, 6.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResultImpl e1ResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.NONE, 2.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResultImpl e1ResultC1 = new CompetitorResultImpl("Comp1", "Comp 1", "Comp 1 Short", "Comp 1 Boat", "Comp 1 Boat Id", 1, MaxPointsReason.DNC, 6.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResultImpl e1ResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", "Comp 2 Short", "Comp 2 Boat", "Comp 2 Boat Id", 2, MaxPointsReason.NONE, 2.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResults e1Results = mockCompetitorResults(e1ResultC1, e1ResultC2);
final CompetitorResultImpl e2ResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResultImpl e2ResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", "Comp 2 Short", "Comp 2 Boat", "Comp 2 Boat Id", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResults e2Results = mockCompetitorResults(e2ResultC2);
final CompetitorResultImpl e3ResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.DSQ, 6.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResultImpl e3ResultC3 = new CompetitorResultImpl("Comp3", "Comp 3", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResultImpl e3ResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", "Comp 2 Short", "Comp 2 Boat", "Comp 2 Boat Id", 2, MaxPointsReason.DSQ, 6.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResultImpl e3ResultC3 = new CompetitorResultImpl("Comp3", "Comp 3", "Comp 3 Short", "Comp 3 Boat", "Comp 3 Boat Id", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResults e3Results = mockCompetitorResults(e3ResultC2, e3ResultC3);
final CompetitorResultImpl e4ResultC1 = new CompetitorResultImpl("Comp1", "Comp 1", 1, /* maxPointsReason */ null, /* score */ null, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResultImpl e4ResultC4 = new CompetitorResultImpl("Comp4", "Comp 4", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResultImpl e4ResultC1 = new CompetitorResultImpl("Comp1", "Comp 1", "Comp 1 Short", "Comp 1 Boat", "Comp 1 Boat Id", 1, /* maxPointsReason */ null, /* score */ null, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResultImpl e4ResultC4 = new CompetitorResultImpl("Comp4", "Comp 4", "Comp 4 Short", "Comp 4 Boat", "Comp 4 Boat Id", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResults e4Results = mockCompetitorResults(e4ResultC1, e4ResultC4);
when(event1.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(e1Results);
when(event2.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(e2Results);
@@ -40,7 +40,13 @@ public interface CompetitorResult extends Serializable {
Serializable getCompetitorId();
String getCompetitorDisplayName();
String getName();
String getShortName();
String getBoatName();
String getBoatSailId();
int getOneBasedRank();
@@ -11,7 +11,9 @@ public class CompetitorResultImpl implements CompetitorResult {
private final Serializable competitorId;
private final String competitorDisplayName;
private final String competitorName;
private final String competitorShortName;
private final int oneBasedRank;
@@ -24,13 +26,32 @@ public class CompetitorResultImpl implements CompetitorResult {
private final String comment;
private final MergeState mergeState;
public CompetitorResultImpl(Serializable competitorId, String competitorDisplayName, int oneBasedRank,
MaxPointsReason maxPointsReason, Double score, TimePoint finishingTime, String comment,
MergeState mergeState) {
private final String boatName;
private final String boatSailId;
public CompetitorResultImpl(CompetitorResult item) {
this(item.getCompetitorId(), item.getName(), item.getShortName(), item.getBoatName(), item.getBoatSailId(),
item.getOneBasedRank(), item.getMaxPointsReason(), item.getScore(), item.getFinishingTime(),
item.getComment(), item.getMergeState());
}
public CompetitorResultImpl(CompetitorResult item, int oneBasedRank) {
this(item.getCompetitorId(), item.getName(), item.getShortName(), item.getBoatName(), item.getBoatSailId(),
oneBasedRank, item.getMaxPointsReason(), item.getScore(), item.getFinishingTime(),
item.getComment(), item.getMergeState());
}
public CompetitorResultImpl(Serializable competitorId, String competitorName, String competitorShortName,
String boatName, String boatSailId, int oneBasedRank, MaxPointsReason maxPointsReason, Double score,
TimePoint finishingTime, String comment, MergeState mergeState) {
super();
this.competitorId = competitorId;
this.competitorDisplayName = competitorDisplayName;
this.competitorName = competitorName;
this.competitorShortName = competitorShortName;
this.boatName = boatName;
this.boatSailId = boatSailId;
this.oneBasedRank = oneBasedRank;
this.maxPointsReason = maxPointsReason;
this.score = score;
@@ -45,10 +66,25 @@ public class CompetitorResultImpl implements CompetitorResult {
}
@Override
public String getCompetitorDisplayName() {
return competitorDisplayName;
public String getName() {
return competitorName;
}
@Override
public String getShortName() {
return competitorShortName;
}
@Override
public String getBoatName() {
return boatName;
}
@Override
public String getBoatSailId() {
return boatSailId;
}
@Override
public int getOneBasedRank() {
return oneBasedRank;
@@ -84,8 +120,11 @@ public class CompetitorResultImpl implements CompetitorResult {
final int prime = 31;
int result = 1;
result = prime * result + ((comment == null) ? 0 : comment.hashCode());
result = prime * result + ((competitorDisplayName == null) ? 0 : competitorDisplayName.hashCode());
result = prime * result + ((competitorName == null) ? 0 : competitorName.hashCode());
result = prime * result + ((competitorShortName == null) ? 0 : competitorShortName.hashCode());
result = prime * result + ((competitorId == null) ? 0 : competitorId.hashCode());
result = prime * result + ((boatName == null) ? 0 : boatName.hashCode());
result = prime * result + ((boatSailId == null) ? 0 : boatSailId.hashCode());
result = prime * result + ((finishingTime == null) ? 0 : finishingTime.hashCode());
result = prime * result + ((maxPointsReason == null) ? 0 : maxPointsReason.hashCode());
result = prime * result + oneBasedRank;
@@ -108,16 +147,26 @@ public class CompetitorResultImpl implements CompetitorResult {
return false;
} else if (!comment.equals(other.comment))
return false;
if (competitorDisplayName == null) {
if (other.competitorDisplayName != null)
if (competitorName == null) {
if (other.competitorName != null)
return false;
} else if (!competitorDisplayName.equals(other.competitorDisplayName))
} else if (!competitorName.equals(other.competitorName))
return false;
if (competitorId == null) {
if (other.competitorId != null)
return false;
} else if (!competitorId.equals(other.competitorId))
return false;
if (boatName == null) {
if (other.boatName != null)
return false;
} else if (!boatName.equals(other.boatName))
return false;
if (boatSailId == null) {
if (other.boatSailId != null)
return false;
} else if (!boatSailId.equals(other.boatSailId))
return false;
if (finishingTime == null) {
if (other.finishingTime != null)
return false;
@@ -142,9 +191,10 @@ public class CompetitorResultImpl implements CompetitorResult {
@Override
public String toString() {
return "CompetitorResultImpl [competitorId=" + competitorId + ", competitorName=" + competitorDisplayName + ", rank="
+ oneBasedRank + ", maxPointsReason=" + maxPointsReason + ", score=" + score + ", finishingTime="
+ finishingTime + ", comment=" + comment + ", mergeState=" + mergeState + "]";
return "CompetitorResultImpl [competitorId=" + competitorId + ", competitorName=" + competitorName
+ ", competitorShortName=" + competitorShortName + ", boatName=" + boatName+ ", boatSailId="
+ boatSailId + ", rank=" + oneBasedRank + ", maxPointsReason=" + maxPointsReason + ", score="+ score
+ ", finishingTime=" + finishingTime + ", comment=" + comment + ", mergeState=" + mergeState + "]";
}
}
@@ -14,7 +14,15 @@ public interface SwissTimingAdapterPersistence {
Iterable<SwissTimingArchiveConfiguration> getSwissTimingArchiveConfigurations();
void storeSwissTimingConfiguration(SwissTimingConfiguration swissTimingConfiguration);
void storeSwissTimingArchiveConfiguration(SwissTimingArchiveConfiguration createSwissTimingArchiveConfiguration);
void createSwissTimingArchiveConfiguration(SwissTimingArchiveConfiguration swissTimingArchiveConfiguration);
void updateSwissTimingArchiveConfiguration(SwissTimingArchiveConfiguration swissTimingArchiveConfiguration);
void deleteSwissTimingArchiveConfiguration(SwissTimingArchiveConfiguration swissTimingArchiveConfiguration);
void deleteSwissTimingConfiguration(String creatorName, String jsonURL);
void updateSwissTimingConfiguration(SwissTimingConfiguration createSwissTimingConfiguration);
void createSwissTimingConfiguration(SwissTimingConfiguration createSwissTimingConfiguration);
}
@@ -3,7 +3,7 @@ package com.sap.sailing.domain.swisstimingadapter.persistence.impl;
public enum FieldNames {
// SwissTiming configuration parameters:
ST_CONFIG_NAME, ST_CONFIG_HOSTNAME, ST_CONFIG_PORT, ST_CONFIG_JSON_URL, ST_CONFIG_UPDATE_URL,
ST_CONFIG_UPDATE_USERNAME, ST_CONFIG_UPDATE_PASSWORD, ST_ARCHIVE_JSON_URL,
ST_CONFIG_UPDATE_USERNAME, ST_CONFIG_UPDATE_PASSWORD, ST_CONFIG_CREATOR_NAME, ST_ARCHIVE_JSON_URL, ST_ARCHIVE_CREATOR_NAME,
// last message count field:
LAST_MESSAGE_COUNT,
@@ -60,7 +60,24 @@ public class SwissTimingAdapterPersistenceImpl implements SwissTimingAdapterPers
String updateURL = (String) object.get(FieldNames.ST_CONFIG_UPDATE_URL.name());
String updateUsername = (String) object.get(FieldNames.ST_CONFIG_UPDATE_USERNAME.name());
String updatePassword = (String) object.get(FieldNames.ST_CONFIG_UPDATE_PASSWORD.name());
return swissTimingFactory.createSwissTimingConfiguration(name, jsonURL, hostname, port, updateURL, updateUsername, updatePassword);
String creatorName = object.getString(FieldNames.ST_CONFIG_CREATOR_NAME.name());
// migration code
final boolean needsUpdate = (creatorName == null);
if (needsUpdate) {
// No creator is set yet -> existing configurations are assumed to belong to the admin
creatorName = "admin";
}
final SwissTimingConfiguration loadedSwissTimingConfiguration = swissTimingFactory.createSwissTimingConfiguration(name, jsonURL, hostname, port, updateURL,
updateUsername, updatePassword, creatorName);
if (needsUpdate) {
// recreating the config on the DB because the composite key changed
deleteSwissTimingConfiguration(null, jsonURL);
createSwissTimingConfiguration(loadedSwissTimingConfiguration);
}
return loadedSwissTimingConfiguration;
}
@Override
@@ -82,12 +99,36 @@ public class SwissTimingAdapterPersistenceImpl implements SwissTimingAdapterPers
}
private SwissTimingArchiveConfiguration loadSwissTimingArchiveConfiguration(Document object) {
return swissTimingFactory.createSwissTimingArchiveConfiguration((String) object.get(FieldNames.ST_ARCHIVE_JSON_URL.name()));
final String jsonUrl = object.getString(FieldNames.ST_ARCHIVE_JSON_URL.name());
String creatorName = object.getString(FieldNames.ST_ARCHIVE_CREATOR_NAME.name());
final boolean needsUpdate = (creatorName == null);
if (needsUpdate) {
// No creator is set yet -> existing configurations are assumed to belong to the admin
creatorName = "admin";
}
final SwissTimingArchiveConfiguration loadedSwissTimingArchiveConfiguration = swissTimingFactory.createSwissTimingArchiveConfiguration(
jsonUrl,
creatorName);
if (needsUpdate) {
// recreating the config on the DB because the composite key changed
deleteSwissTimingArchiveConfiguration(
swissTimingFactory.createSwissTimingArchiveConfiguration(jsonUrl, null));
createSwissTimingArchiveConfiguration(loadedSwissTimingArchiveConfiguration);
}
return loadedSwissTimingArchiveConfiguration;
}
@Override
public void storeSwissTimingConfiguration(SwissTimingConfiguration swissTimingConfiguration) {
public void createSwissTimingConfiguration(SwissTimingConfiguration swissTimingConfiguration) {
MongoCollection<org.bson.Document> stConfigCollection = database.getCollection(CollectionNames.SWISSTIMING_CONFIGURATIONS.name());
Document result = storeSwissTimingConfiguration(swissTimingConfiguration);
stConfigCollection.withWriteConcern(WriteConcern.ACKNOWLEDGED).insertOne(result);
}
private Document storeSwissTimingConfiguration(SwissTimingConfiguration swissTimingConfiguration) {
Document result = new Document();
result.put(FieldNames.ST_CONFIG_NAME.name(), swissTimingConfiguration.getName());
result.put(FieldNames.ST_CONFIG_JSON_URL.name(), swissTimingConfiguration.getJsonURL());
@@ -96,19 +137,63 @@ public class SwissTimingAdapterPersistenceImpl implements SwissTimingAdapterPers
result.put(FieldNames.ST_CONFIG_UPDATE_URL.name(), swissTimingConfiguration.getUpdateURL());
result.put(FieldNames.ST_CONFIG_UPDATE_USERNAME.name(), swissTimingConfiguration.getUpdateUsername());
result.put(FieldNames.ST_CONFIG_UPDATE_PASSWORD.name(), swissTimingConfiguration.getUpdatePassword());
final Document updateQuery = new Document(FieldNames.ST_CONFIG_JSON_URL.name(),
swissTimingConfiguration.getJsonURL());
stConfigCollection.withWriteConcern(WriteConcern.ACKNOWLEDGED).replaceOne(updateQuery, result,
new UpdateOptions().upsert(true));
result.put(FieldNames.ST_CONFIG_CREATOR_NAME.name(), swissTimingConfiguration.getCreatorName());
return result;
}
@Override
public void storeSwissTimingArchiveConfiguration(
SwissTimingArchiveConfiguration createSwissTimingArchiveConfiguration) {
public void createSwissTimingArchiveConfiguration(
SwissTimingArchiveConfiguration config) {
MongoCollection<org.bson.Document> stArchiveConfigCollection = database.getCollection(CollectionNames.SWISSTIMING_ARCHIVE_CONFIGURATIONS.name());
Document result = new Document();
result.put(FieldNames.ST_ARCHIVE_JSON_URL.name(), createSwissTimingArchiveConfiguration.getJsonURL());
Document result = storeSwissTimingArchiveConfiguration(config);
stArchiveConfigCollection.withWriteConcern(WriteConcern.ACKNOWLEDGED).insertOne(result);
}
@Override
public void updateSwissTimingArchiveConfiguration(
SwissTimingArchiveConfiguration config) {
MongoCollection<org.bson.Document> stArchiveConfigCollection = database
.getCollection(CollectionNames.SWISSTIMING_ARCHIVE_CONFIGURATIONS.name());
Document result = storeSwissTimingArchiveConfiguration(config);
stArchiveConfigCollection.withWriteConcern(WriteConcern.ACKNOWLEDGED).replaceOne(result, result,
new UpdateOptions().upsert(true));
}
private Document storeSwissTimingArchiveConfiguration(SwissTimingArchiveConfiguration config) {
Document result = new Document();
result.put(FieldNames.ST_ARCHIVE_JSON_URL.name(), config.getJsonURL());
result.put(FieldNames.ST_ARCHIVE_CREATOR_NAME.name(), config.getCreatorName());
return result;
}
@Override
public void deleteSwissTimingArchiveConfiguration(
SwissTimingArchiveConfiguration config) {
MongoCollection<org.bson.Document> stArchiveConfigCollection = database
.getCollection(CollectionNames.SWISSTIMING_ARCHIVE_CONFIGURATIONS.name());
Document result = storeSwissTimingArchiveConfiguration(config);
stArchiveConfigCollection.withWriteConcern(WriteConcern.ACKNOWLEDGED).deleteOne(result);
}
@Override
public void deleteSwissTimingConfiguration(String creatorName, String jsonURL) {
MongoCollection<org.bson.Document> stConfigCollection = database
.getCollection(CollectionNames.SWISSTIMING_CONFIGURATIONS.name());
Document deleteQuery = new Document(FieldNames.ST_CONFIG_JSON_URL.name(), jsonURL);
deleteQuery.put(FieldNames.ST_CONFIG_CREATOR_NAME.name(), creatorName);
stConfigCollection.withWriteConcern(WriteConcern.ACKNOWLEDGED).deleteMany(deleteQuery);
}
@Override
public void updateSwissTimingConfiguration(SwissTimingConfiguration config) {
MongoCollection<org.bson.Document> stConfigCollection = database
.getCollection(CollectionNames.SWISSTIMING_CONFIGURATIONS.name());
Document result = storeSwissTimingConfiguration(config);
Document updateQuery = new Document(FieldNames.ST_CONFIG_JSON_URL.name(), config.getJsonURL());
updateQuery.put(FieldNames.ST_CONFIG_CREATOR_NAME.name(), config.getCreatorName());
stConfigCollection.withWriteConcern(WriteConcern.ACKNOWLEDGED).replaceOne(updateQuery, result,
new UpdateOptions().upsert(true));
}
}
@@ -10,6 +10,8 @@ public interface SwissTimingArchiveConfiguration extends WithQualifiedObjectIden
String getJsonURL();
String getCreatorName();
@Override
default String getName() {
return getJsonURL();
@@ -26,10 +28,11 @@ public interface SwissTimingArchiveConfiguration extends WithQualifiedObjectIden
}
default TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier() {
return getTypeRelativeObjectIdentifier(getJsonURL());
return getTypeRelativeObjectIdentifier(getJsonURL(), getCreatorName());
}
public static TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier(String jsonUrl) {
return new TypeRelativeObjectIdentifier(jsonUrl);
public static TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier(String jsonUrl, String username) {
return username == null ? new TypeRelativeObjectIdentifier(jsonUrl)
: new TypeRelativeObjectIdentifier(jsonUrl, username);
}
}
@@ -13,6 +13,8 @@ import com.sap.sse.security.shared.WithQualifiedObjectIdentifier;
*
*/
public interface SwissTimingConfiguration extends WithQualifiedObjectIdentifier {
String getCreatorName();
String getName();
String getJsonURL();
@@ -38,10 +40,11 @@ public interface SwissTimingConfiguration extends WithQualifiedObjectIdentifier
}
default TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier() {
return getTypeRelativeObjectIdentifier(getJsonURL());
return getTypeRelativeObjectIdentifier(getJsonURL(), getCreatorName());
}
public static TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier(String jsonUrl) {
return new TypeRelativeObjectIdentifier(jsonUrl);
public static TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier(String jsonUrl, String username) {
return username == null ? new TypeRelativeObjectIdentifier(jsonUrl)
: new TypeRelativeObjectIdentifier(jsonUrl, username);
}
}
@@ -53,7 +53,8 @@ public interface SwissTimingFactory {
SailMasterTransceiver createSailMasterTransceiver();
SwissTimingConfiguration createSwissTimingConfiguration(String name, String jsonURL, String hostname, Integer port, String updateURL, String updateUsername, String updatePassword);
SwissTimingConfiguration createSwissTimingConfiguration(String name, String jsonURL, String hostname, Integer port,
String updateURL, String updateUsername, String updatePassword, String creatorName);
SwissTimingRaceTracker createRaceTracker(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore,
DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, SwissTimingTrackingConnectivityParameters connectivityParams,
@@ -69,6 +70,6 @@ public interface SwissTimingFactory {
SailMasterMessage createMessage(String message);
SwissTimingArchiveConfiguration createSwissTimingArchiveConfiguration(String string);
SwissTimingArchiveConfiguration createSwissTimingArchiveConfiguration(String jsonUrl, String creatorName);
}
@@ -5,14 +5,21 @@ import com.sap.sailing.domain.swisstimingadapter.SwissTimingArchiveConfiguration
public class SwissTimingArchiveConfigurationImpl implements SwissTimingArchiveConfiguration {
private static final long serialVersionUID = 1L;
private final String jsonUrl;
private final String creatorName;
public SwissTimingArchiveConfigurationImpl(String jsonUrl) {
public SwissTimingArchiveConfigurationImpl(String jsonUrl, String creatorName) {
super();
this.jsonUrl = jsonUrl;
this.creatorName = creatorName;
}
@Override
public String getJsonURL() {
return jsonUrl;
}
@Override
public String getCreatorName() {
return creatorName;
}
}
@@ -11,8 +11,10 @@ public class SwissTimingConfigurationImpl implements SwissTimingConfiguration {
private final String updateURL;
private final String updateUsername;
private final String updatePassword;
private String creatorName;
public SwissTimingConfigurationImpl(String name, String jsonURL, String hostname, Integer port, String updateURL, String updateUsername, String updatePassword) {
public SwissTimingConfigurationImpl(String name, String jsonURL, String hostname, Integer port, String updateURL,
String updateUsername, String updatePassword, String creatorName) {
super();
this.name = name;
this.jsonURL = jsonURL;
@@ -21,6 +23,12 @@ public class SwissTimingConfigurationImpl implements SwissTimingConfiguration {
this.updateURL = updateURL;
this.updateUsername = updateUsername;
this.updatePassword = updatePassword;
this.creatorName = creatorName;
}
@Override
public String getCreatorName() {
return creatorName;
}
@Override
@@ -85,8 +85,9 @@ public class SwissTimingFactoryImpl implements SwissTimingFactory {
@Override
public SwissTimingConfiguration createSwissTimingConfiguration(String name, String jsonURL, String hostname,
Integer port, String updateURL, String updateUsername, String updatePassword) {
return new SwissTimingConfigurationImpl(name, jsonURL, hostname, port, updateURL, updateUsername, updatePassword);
Integer port, String updateURL, String updateUsername, String updatePassword, String creatorName) {
return new SwissTimingConfigurationImpl(name, jsonURL, hostname, port, updateURL, updateUsername,
updatePassword, creatorName);
}
@Override
@@ -125,7 +126,7 @@ public class SwissTimingFactoryImpl implements SwissTimingFactory {
}
@Override
public SwissTimingArchiveConfiguration createSwissTimingArchiveConfiguration(String jsonUrl) {
return new SwissTimingArchiveConfigurationImpl(jsonUrl);
public SwissTimingArchiveConfiguration createSwissTimingArchiveConfiguration(String jsonUrl, String creatorName) {
return new SwissTimingArchiveConfigurationImpl(jsonUrl, creatorName);
}
}
@@ -153,8 +153,8 @@ public class MarkPassingUpdateFromRaceLogFinishPositioingListTest extends Abstra
final CompetitorResults results = new CompetitorResultsImpl();
int rank = 1;
for (Pair<Competitor, TimePoint> finishingTime : finishingTimes) {
results.add(new CompetitorResultImpl(finishingTime.getA().getId(), finishingTime.getA().getName(), rank++, /* maxPointsReason */
null, /* score */null, finishingTime.getB(), /* comment */null, MergeState.OK));
results.add(new CompetitorResultImpl(finishingTime.getA().getId(), finishingTime.getA().getName(), finishingTime.getA().getShortName(),
"BoatName", "BoatSailId", rank++, /* maxPointsReason */ null, /* score */null, finishingTime.getB(), /* comment */null, MergeState.OK));
}
return results;
}
@@ -21,6 +21,12 @@ public interface MongoObjectFactory {
* {@link DomainObjectFactory#getTracTracConfigurations()} called for the same <code>database</code>
* will return an equal <code>tracTracConfiguration</code> in its results.
*/
void storeTracTracConfiguration(TracTracConfiguration tracTracConfiguration);
void createTracTracConfiguration(TracTracConfiguration tracTracConfiguration);
void updateTracTracConfiguration(TracTracConfiguration tracTracConfiguration);
void deleteTracTracConfiguration(String creatorName, String jsonurl);
void clear();
}
@@ -8,4 +8,6 @@ public interface PersistenceFactory {
PersistenceFactory INSTANCE = new PersistenceFactoryImpl();
DomainObjectFactory createDomainObjectFactory(MongoDatabase db, DomainFactory tracTracDomainFactory);
MongoObjectFactory createMongoObjectFactory(MongoDatabase db);
}
@@ -24,6 +24,7 @@ import com.sap.sailing.domain.tractracadapter.impl.RaceTrackingConnectivityParam
import com.sap.sse.common.TypeBasedServiceFinder;
import com.sap.sse.mongodb.MongoDBService;
import com.sap.sse.security.SecurityService;
import com.sap.sse.util.ClearStateTestSupport;
import com.sap.sse.util.ServiceTrackerFactory;
public class Activator implements BundleActivator {
@@ -61,6 +62,8 @@ public class Activator implements BundleActivator {
com.sap.sailing.domain.tractracadapter.persistence.DomainObjectFactory tractracDomainObjectFactory = com.sap.sailing.domain.tractracadapter.persistence.PersistenceFactory.INSTANCE
.createDomainObjectFactory(mongoObjectFactory.getDatabase(), domainFactory);
com.sap.sailing.domain.tractracadapter.persistence.MongoObjectFactory tractracMongoObjectFactory = com.sap.sailing.domain.tractracadapter.persistence.PersistenceFactory.INSTANCE
.createMongoObjectFactory(mongoObjectFactory.getDatabase());
for (TracTracConfiguration trackTrackConfig : tractracDomainObjectFactory.getTracTracConfigurations()) {
securityService.migrateOwnership(trackTrackConfig);
}
@@ -69,6 +72,12 @@ public class Activator implements BundleActivator {
properties.put(TypeBasedServiceFinder.TYPE, RaceTrackingConnectivityParametersImpl.TYPE);
context.registerService(RaceTrackingConnectivityParametersHandler.class, paramsHandler, properties);
context.registerService(ClearStateTestSupport.class.getName(), new ClearStateTestSupport() {
@Override
public void clearState() throws Exception {
tractracMongoObjectFactory.clear();
}
}, null);
} catch (Exception e) {
logger.log(Level.SEVERE, "Exception trying to register TracTrac RaceTrackingConnectivityParametersHandler implementation", e);
}
@@ -51,14 +51,31 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
String courseDesignUpdateUri = courseDesignUpdateUriObject == null ? "" : (String) courseDesignUpdateUriObject;
String tracTracUsername = tracTracUsernameObject == null ? "" : (String) tracTracUsernameObject;
String tracTracPassword = tracTracPasswordObject == null ? "" : (String) tracTracPasswordObject;
String creatorName = (String) object.get(FieldNames.TT_CONFIG_CREATOR_NAME.name());
String jsonURL = (String) object.get(FieldNames.TT_CONFIG_JSON_URL.name());
return tracTracDomainFactory.createTracTracConfiguration((String) object.get(FieldNames.TT_CONFIG_NAME.name()),
(String) object.get(FieldNames.TT_CONFIG_JSON_URL.name()),
final boolean needsUpdate = (creatorName == null);
if (needsUpdate) {
// No creator is set yet -> existing configurations are assumed to belong to the admin
creatorName = "admin";
}
final TracTracConfiguration loadedTracTracConfiguration = tracTracDomainFactory.createTracTracConfiguration(
creatorName,
(String) object.get(FieldNames.TT_CONFIG_NAME.name()),
jsonURL,
(String) object.get(FieldNames.TT_CONFIG_LIVE_DATA_URI.name()),
(String) object.get(FieldNames.TT_CONFIG_STORED_DATA_URI.name()),
courseDesignUpdateUri,
tracTracUsername,
tracTracPassword);
if (needsUpdate) {
// recreating the config on the DB because the composite key changed
new MongoObjectFactoryImpl(database).deleteTracTracConfiguration(null, jsonURL);
new MongoObjectFactoryImpl(database).createTracTracConfiguration(loadedTracTracConfiguration);
}
return loadedTracTracConfiguration;
}
}
@@ -2,6 +2,6 @@ package com.sap.sailing.domain.tractracadapter.persistence.impl;
public enum FieldNames {
// TracTrac configuration parameters:
TT_CONFIG_NAME, TT_CONFIG_JSON_URL, TT_CONFIG_LIVE_DATA_URI, TT_CONFIG_STORED_DATA_URI, TT_CONFIG_COURSE_DESIGN_UPDATE_URI, TT_CONFIG_TRACTRAC_USERNAME, TT_CONFIG_TRACTRAC_PASSWORD,
TT_CONFIG_CREATOR_NAME, TT_CONFIG_NAME, TT_CONFIG_JSON_URL, TT_CONFIG_LIVE_DATA_URI, TT_CONFIG_STORED_DATA_URI, TT_CONFIG_COURSE_DESIGN_UPDATE_URI, TT_CONFIG_TRACTRAC_USERNAME, TT_CONFIG_TRACTRAC_PASSWORD,
}
@@ -16,11 +16,36 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
super();
this.database = database;
}
@Override
public void clear() {
database.getCollection(CollectionNames.TRACTRAC_CONFIGURATIONS.name())
.withWriteConcern(WriteConcern.ACKNOWLEDGED).drop();
}
@Override
public void storeTracTracConfiguration(TracTracConfiguration tracTracConfiguration) {
public void createTracTracConfiguration(TracTracConfiguration tracTracConfiguration) {
MongoCollection<Document> ttConfigCollection = database.getCollection(CollectionNames.TRACTRAC_CONFIGURATIONS.name());
final Document result = storeTracTracConfiguration(tracTracConfiguration);
ttConfigCollection.withWriteConcern(WriteConcern.ACKNOWLEDGED).insertOne(result);
}
@Override
public void updateTracTracConfiguration(TracTracConfiguration tracTracConfiguration) {
MongoCollection<Document> ttConfigCollection = database
.getCollection(CollectionNames.TRACTRAC_CONFIGURATIONS.name());
final Document result = storeTracTracConfiguration(tracTracConfiguration);
// Object with given name is updated or created if it does not exist yet
final Document updateQuery = new Document(FieldNames.TT_CONFIG_JSON_URL.name(),
tracTracConfiguration.getJSONURL());
updateQuery.put(FieldNames.TT_CONFIG_CREATOR_NAME.name(), tracTracConfiguration.getCreatorName());
ttConfigCollection.withWriteConcern(WriteConcern.ACKNOWLEDGED).replaceOne(updateQuery, result,
new UpdateOptions().upsert(true));
}
private Document storeTracTracConfiguration(TracTracConfiguration tracTracConfiguration) {
final Document result = new Document();
result.put(FieldNames.TT_CONFIG_CREATOR_NAME.name(), tracTracConfiguration.getCreatorName());
result.put(FieldNames.TT_CONFIG_NAME.name(), tracTracConfiguration.getName());
result.put(FieldNames.TT_CONFIG_JSON_URL.name(), tracTracConfiguration.getJSONURL());
result.put(FieldNames.TT_CONFIG_LIVE_DATA_URI.name(), tracTracConfiguration.getLiveDataURI());
@@ -28,10 +53,15 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
result.put(FieldNames.TT_CONFIG_COURSE_DESIGN_UPDATE_URI.name(), tracTracConfiguration.getCourseDesignUpdateURI());
result.put(FieldNames.TT_CONFIG_TRACTRAC_USERNAME.name(), tracTracConfiguration.getTracTracUsername());
result.put(FieldNames.TT_CONFIG_TRACTRAC_PASSWORD.name(), tracTracConfiguration.getTracTracPassword());
// Object with given name is updated or created if it does not exist yet
final Document updateQuery = new Document(FieldNames.TT_CONFIG_JSON_URL.name(),
tracTracConfiguration.getJSONURL());
ttConfigCollection.withWriteConcern(WriteConcern.ACKNOWLEDGED).replaceOne(updateQuery, result,
new UpdateOptions().upsert(true));
return result;
}
@Override
public void deleteTracTracConfiguration(String creatorName, String jsonUrl) {
MongoCollection<Document> ttConfigCollection = database.getCollection(CollectionNames.TRACTRAC_CONFIGURATIONS.name());
final Document deleteQuery = new Document(FieldNames.TT_CONFIG_JSON_URL.name(),
jsonUrl);
deleteQuery.put(FieldNames.TT_CONFIG_CREATOR_NAME.name(), creatorName);
ttConfigCollection.withWriteConcern(WriteConcern.ACKNOWLEDGED).deleteOne(deleteQuery);
}
}
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.tractracadapter.persistence.impl;
import com.mongodb.client.MongoDatabase;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sailing.domain.tractracadapter.persistence.DomainObjectFactory;
import com.sap.sailing.domain.tractracadapter.persistence.MongoObjectFactory;
import com.sap.sailing.domain.tractracadapter.persistence.PersistenceFactory;
public class PersistenceFactoryImpl implements PersistenceFactory {
@@ -10,4 +11,9 @@ public class PersistenceFactoryImpl implements PersistenceFactory {
public DomainObjectFactory createDomainObjectFactory(MongoDatabase db, DomainFactory tracTracDomainFactory) {
return new DomainObjectFactoryImpl(db, tracTracDomainFactory);
}
@Override
public MongoObjectFactory createMongoObjectFactory(MongoDatabase db) {
return new MongoObjectFactoryImpl(db);
}
}
@@ -246,7 +246,9 @@ public interface DomainFactory {
*/
void updateCourseWaypoints(Course courseToUpdate, Iterable<Util.Pair<TracTracControlPoint, PassingInstruction>> controlPoints) throws PatchFailedException;
TracTracConfiguration createTracTracConfiguration(String name, String jsonURL, String liveDataURI, String storedDataURI, String courseDesignUpdateURI, String tracTracUsername, String tracTracPassword);
TracTracConfiguration createTracTracConfiguration(String creatorName, String name, String jsonURL,
String liveDataURI, String storedDataURI, String courseDesignUpdateURI, String tracTracUsername,
String tracTracPassword);
/**
* Fetch the race definition for <code>race</code>. If the race definition hasn't been created yet, the call blocks
@@ -74,8 +74,6 @@ public interface TracTracAdapter {
* described above will be created and used.
* @param offsetToStartTimeOfSimulatedRace
* if non-<code>null</code>, the {@link Simulator} will be used with this duration as start offset
* @param trackWind TODO
* @param correctWindDirectionByMagneticDeclination TODO
*/
RaceHandle addTracTracRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, URL paramURL,
URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint trackingStartTime,
@@ -103,7 +101,8 @@ public interface TracTracAdapter {
RaceRecord getSingleTracTracRaceRecord(URL jsonURL, String raceId, boolean loadClientParams) throws Exception;
TracTracConfiguration createTracTracConfiguration(String name, String jsonURL, String liveDataURI,
TracTracConfiguration createTracTracConfiguration(String creatorName, String name, String jsonURL,
String liveDataURI,
String storedDataURI, String courseDesignUpdateURI, String tracTracUsername, String tracTracPassword);
}
@@ -39,6 +39,8 @@ public interface TracTracConfiguration extends WithQualifiedObjectIdentifier {
*/
String getTracTracPassword();
String getCreatorName();
@Override
default QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
@@ -50,10 +52,12 @@ public interface TracTracConfiguration extends WithQualifiedObjectIdentifier {
}
default TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier() {
return getTypeRelativeObjectIdentifier(getJSONURL());
return getTypeRelativeObjectIdentifier(getJSONURL(), getCreatorName());
}
public static TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier(String jsonUrl) {
return new TypeRelativeObjectIdentifier(jsonUrl);
public static TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier(String jsonUrl, String username) {
return username == null ? new TypeRelativeObjectIdentifier(jsonUrl)
: new TypeRelativeObjectIdentifier(jsonUrl, username);
}
}
@@ -998,8 +998,11 @@ public class DomainFactoryImpl implements DomainFactory {
}
@Override
public TracTracConfiguration createTracTracConfiguration(String name, String jsonURL, String liveDataURI, String storedDataURI, String courseDesignUpdateURI, String tracTracUsername, String tracTracPassword) {
return new TracTracConfigurationImpl(name, jsonURL, liveDataURI, storedDataURI, courseDesignUpdateURI, tracTracUsername, tracTracPassword);
public TracTracConfiguration createTracTracConfiguration(String creatorName, String name, String jsonURL,
String liveDataURI, String storedDataURI, String courseDesignUpdateURI, String tracTracUsername,
String tracTracPassword) {
return new TracTracConfigurationImpl(creatorName, name, jsonURL, liveDataURI, storedDataURI,
courseDesignUpdateURI, tracTracUsername, tracTracPassword);
}
@Override
@@ -99,9 +99,11 @@ public class TracTracAdapterImpl implements TracTracAdapter {
}
@Override
public TracTracConfiguration createTracTracConfiguration(String name, String jsonURL, String liveDataURI,
public TracTracConfiguration createTracTracConfiguration(String creatorName, String name, String jsonURL,
String liveDataURI,
String storedDataURI, String courseDesignUpdateURI, String tracTracUsername, String tracTracPassword) {
return getTracTracDomainFactory().createTracTracConfiguration(name, jsonURL, liveDataURI, storedDataURI,
return getTracTracDomainFactory().createTracTracConfiguration(creatorName, name, jsonURL, liveDataURI,
storedDataURI,
courseDesignUpdateURI, tracTracUsername, tracTracPassword);
}
@@ -11,9 +11,12 @@ public class TracTracConfigurationImpl implements TracTracConfiguration {
private final String courseDesignUpdateURI;
private final String tracTracUsername;
private final String tracTracPassword;
private final String creatorName;
public TracTracConfigurationImpl(String name, String jsonURL, String liveDataURI, String storedDataURI, String courseDesignUpdateURI,
public TracTracConfigurationImpl(String creatorName, String name, String jsonURL, String liveDataURI,
String storedDataURI, String courseDesignUpdateURI,
String tracTracUsername, String tracTracPassword) {
this.creatorName = creatorName;
this.name = name;
this.jsonURL = jsonURL;
this.liveDataURI = liveDataURI;
@@ -67,4 +70,8 @@ public class TracTracConfigurationImpl implements TracTracConfiguration {
return tracTracPassword;
}
public String getCreatorName() {
return creatorName;
}
}
@@ -465,10 +465,12 @@ public final class HomeServiceUtil {
URL baseURL = getBaseURL(serverRef.getURL());
if (remoteEvents != null) {
for (EventBase remoteEvent : remoteEvents) {
if (remoteEvent.isPublic() && securityService.hasCurrentUserReadPermission(remoteEvent)) {
for(EventVisitor visitor : visitors) {
visitor.visit(remoteEvent, true, baseURL);
}
// Those events have been publicly advertised and have been received by an unauthenticated
// anonymous call to the /events end point to the remote server. No security checks or filtering
// seems necessary here because we should be seeing only the public remote events anyway.
// See also bug 5000.
for (EventVisitor visitor : visitors) {
visitor.visit(remoteEvent, true, baseURL);
}
}
}
@@ -291,22 +291,28 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint
/* CONNECTORS */
final HorizontalTabLayoutPanel connectorsTabPanel = panel.addVerticalTab(getStringMessages().connectors(), "TrackingProviderPanel");
TracTracEventManagementPanel tractracEventManagementPanel = new TracTracEventManagementPanel(
getSailingService(), getUserService(), this, this, getStringMessages());
getSailingService(), getUserService(), this, this, getStringMessages(), tableResources);
tractracEventManagementPanel.ensureDebugId("TracTracEventManagement");
panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel<TracTracEventManagementPanel>(tractracEventManagementPanel),
panel.addToTabPanel(connectorsTabPanel,
new DefaultRefreshableAdminConsolePanel<TracTracEventManagementPanel>(tractracEventManagementPanel) {
@Override
public void refreshAfterBecomingVisible() {
tractracEventManagementPanel.refreshTracTracConnectors();
}
},
getStringMessages().tracTracEvents(),
SecuredDomainType.TRACTRAC_ACCOUNT.getPermission(DefaultActions.values()));
regattasDisplayers.add(tractracEventManagementPanel);
SwissTimingReplayConnectorPanel swissTimingReplayConnectorPanel = new SwissTimingReplayConnectorPanel(
getSailingService(), getUserService(), this, this, getStringMessages());
getSailingService(), getUserService(), this, this, getStringMessages(), tableResources);
panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel<SwissTimingReplayConnectorPanel>(swissTimingReplayConnectorPanel),
getStringMessages().swissTimingArchiveConnector(),
SecuredDomainType.SWISS_TIMING_ARCHIVE_ACCOUNT.getPermission(DefaultActions.values()));
regattasDisplayers.add(swissTimingReplayConnectorPanel);
SwissTimingEventManagementPanel swisstimingEventManagementPanel = new SwissTimingEventManagementPanel(
getSailingService(), getUserService(), this, this, getStringMessages());
getSailingService(), getUserService(), this, this, getStringMessages(), tableResources);
panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel<SwissTimingEventManagementPanel>(swisstimingEventManagementPanel),
getStringMessages().swissTimingEvents(),
SecuredDomainType.SWISS_TIMING_ACCOUNT.getPermission(DefaultActions.values()));
@@ -57,6 +57,7 @@ import com.sap.sse.security.shared.HasPermissions;
import com.sap.sse.security.shared.HasPermissions.DefaultActions;
import com.sap.sse.security.ui.client.UserService;
import com.sap.sse.security.ui.client.component.AccessControlledActionsColumn;
import com.sap.sse.security.ui.client.component.AccessControlledButtonPanel;
import com.sap.sse.security.ui.client.component.DefaultActionsImagesBarCell;
import com.sap.sse.security.ui.client.component.EditOwnershipDialog;
import com.sap.sse.security.ui.client.component.EditOwnershipDialog.DialogConfig;
@@ -122,39 +123,34 @@ public class IgtimiAccountsPanel extends FlowPanel {
final RefreshableMultiSelectionModel<AccountWithSecurityDTO> refreshableAccountsSelectionModel = (RefreshableMultiSelectionModel<AccountWithSecurityDTO>) cellTable
.getSelectionModel();
// setup controls
final Panel controlsPanel = new HorizontalPanel();
final Button removeAccountButton = new Button(stringMessages.remove());
removeAccountButton.setEnabled(false);
removeAccountButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if (refreshableAccountsSelectionModel.getSelectedSet().size() > 0) {
if (Window.confirm(stringMessages.doYouReallyWantToRemoveTheSelectedIgtimiAccounts())) {
for (AccountWithSecurityDTO account : refreshableAccountsSelectionModel.getSelectedSet()) {
removeAccount(account, filteredAccounts);
}
controlsPanel.add(filterAccountsPanel);
final AccessControlledButtonPanel buttonPanel = new AccessControlledButtonPanel(userService,
SecuredDomainType.IGTIMI_ACCOUNT);
controlsPanel.add(buttonPanel);
buttonPanel.addUnsecuredAction(stringMessages.refresh(), () -> refresh());
// setup controls
final Button removeAccountButton = buttonPanel.addRemoveAction(stringMessages.remove(), () -> {
if (refreshableAccountsSelectionModel.getSelectedSet().size() > 0) {
if (Window.confirm(stringMessages.doYouReallyWantToRemoveTheSelectedIgtimiAccounts())) {
for (AccountWithSecurityDTO account : refreshableAccountsSelectionModel.getSelectedSet()) {
removeAccount(account, filteredAccounts);
}
}
}
});
removeAccountButton.setEnabled(false);
refreshableAccountsSelectionModel.addSelectionChangeHandler(
e -> removeAccountButton.setEnabled(refreshableAccountsSelectionModel.getSelectedSet().size() > 0));
controlsPanel.add(filterAccountsPanel);
controlsPanel.add(removeAccountButton);
add(controlsPanel);
add(cellTable);
// add button
final Button addAccountButton = new Button(stringMessages.addIgtimiAccount());
final Button addAccountButton = buttonPanel.addCreateAction(stringMessages.addIgtimiAccount(),
() -> addAccount());
addAccountButton.ensureDebugId("addIgtimiAccount");
addAccountButton.addClickHandler(e -> addAccount());
add(addAccountButton);
// refresh button
final Button refreshButton = new Button(stringMessages.refresh());
refreshButton.addClickHandler(e -> refresh());
add(refreshButton);
final String protocol = Window.Location.getProtocol().endsWith(":")
? Window.Location.getProtocol().substring(0, Window.Location.getProtocol().length() - 1)
@@ -171,8 +167,7 @@ public class IgtimiAccountsPanel extends FlowPanel {
@Override
public void onSuccess(String result) {
final Button addIgtimiUserButton = new Button(stringMessages.addIgtimiUser() + " (OAuth)");
addIgtimiUserButton.addClickHandler(clickEvent -> {
buttonPanel.addCreateAction(stringMessages.addIgtimiUser() + " (OAuth)", () -> {
Frame frame = new Frame(UriUtils.fromString(result).asString());
frame.addLoadHandler(loadEvent -> refresh());
frame.setPixelSize(520, 770);
@@ -195,8 +190,7 @@ public class IgtimiAccountsPanel extends FlowPanel {
refresh();
});
dialogBox.center();
});
controlsPanel.add(addIgtimiUserButton);
});
}
});
}
@@ -228,6 +222,8 @@ public class IgtimiAccountsPanel extends FlowPanel {
account -> account.getName(), columnSortHandler);
final TextColumn<AccountWithSecurityDTO> accountEmailColumn = new AbstractSortableTextColumn<AccountWithSecurityDTO>(
account -> account.getEmail(), columnSortHandler);
final TextColumn<AccountWithSecurityDTO> creatorNameColumn = new AbstractSortableTextColumn<AccountWithSecurityDTO>(
account -> account.getCreatorName(), columnSortHandler);
final HasPermissions type = SecuredDomainType.IGTIMI_ACCOUNT;
final AccessControlledActionsColumn<AccountWithSecurityDTO, DefaultActionsImagesBarCell> roleActionColumn = create(
@@ -248,6 +244,7 @@ public class IgtimiAccountsPanel extends FlowPanel {
table.addColumn(accountSelectionCheckboxColumn, accountSelectionCheckboxColumn.getHeader());
table.addColumn(accountNameColumn, stringMessages.name());
table.addColumn(accountEmailColumn, stringMessages.email());
table.addColumn(creatorNameColumn, stringMessages.creatorName());
SecuredDTOOwnerColumn.configureOwnerColumns(table, columnSortHandler, stringMessages);
table.addColumn(roleActionColumn, stringMessages.actions());
table.setSelectionModel(accountSelectionCheckboxColumn.getSelectionModel(),
@@ -1,16 +1,10 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.cellview.client.AbstractCellTable;
@@ -22,33 +16,35 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CaptionPanel;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.IntegerBox;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.view.client.ListDataProvider;
import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.RegattaName;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sailing.gwt.ui.adminconsole.swisstiming.EditSwissTimingConnectionDialog;
import com.sap.sailing.gwt.ui.adminconsole.swisstiming.SwissTimingConnectionTableWrapper;
import com.sap.sailing.gwt.ui.client.RegattaRefresher;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingConfigurationWithSecurityDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingEventRecordDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingRaceRecordDTO;
import com.sap.sse.common.util.NaturalComparator;
import com.sap.sse.gwt.adminconsole.AdminConsoleTableResources;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.gwt.client.celltable.CellTableWithCheckboxResources;
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
import com.sap.sse.gwt.client.celltable.FlushableCellTable;
import com.sap.sse.gwt.client.celltable.SelectionCheckboxColumn;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel;
import com.sap.sse.security.ui.client.UserService;
import com.sap.sse.security.ui.client.component.AccessControlledButtonPanel;
/**
* Allows the user to start and stop tracking of races using the SwissTiming connector. In particular,
@@ -64,24 +60,12 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
private final LabeledAbstractFilterablePanel<SwissTimingRaceRecordDTO> filterablePanelEvents;
private final ListDataProvider<SwissTimingRaceRecordDTO> raceList;
private final FlushableCellTable<SwissTimingRaceRecordDTO> raceTable;
private final Map<String, SwissTimingConfigurationDTO> previousConfigurations;
private final ListBox previousConfigurationsComboBox;
private final TextBox eventIdBox;
private final TextBox jsonUrlBox;
private final TextBox updateURLBox;
private final TextBox updateUsernameBox;
private final PasswordTextBox updatePasswordBox;
private final TextBox hostnameTextbox;
private final IntegerBox portIntegerbox;
private final List<SwissTimingRaceRecordDTO> availableSwissTimingRaces = new ArrayList<SwissTimingRaceRecordDTO>();
private final String manage2sailBaseAPIUrl = "http://manage2sail.com/api/public/links/event/";
private final String manage2sailAPIaccessToken = "?accesstoken=bDAv8CwsTM94ujZ";
private final String manage2sailUrlAppendix = "&mediaType=json&includeRaces=true";
private final String eventIdPattern = "[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}";
private final SwissTimingConnectionTableWrapper connectionsTable;
public SwissTimingEventManagementPanel(final SailingServiceAsync sailingService, UserService userService,
ErrorReporter errorReporter,
RegattaRefresher regattaRefresher, StringMessages stringConstants) {
RegattaRefresher regattaRefresher, StringMessages stringConstants, final CellTableWithCheckboxResources tableResources) {
super(sailingService, userService, regattaRefresher, errorReporter, true, stringConstants);
this.errorReporter = errorReporter;
VerticalPanel mainPanel = new VerticalPanel();
@@ -92,101 +76,92 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
VerticalPanel verticalPanel = new VerticalPanel();
captionPanelConnections.setContentWidget(verticalPanel);
captionPanelConnections.setStyleName("bold");
Grid connectionsGrid = new Grid(9, 2);
verticalPanel.add(connectionsGrid);
previousConfigurations = new HashMap<String, SwissTimingConfigurationDTO>();
previousConfigurationsComboBox = new ListBox();
connectionsGrid.setWidget(0, 1, previousConfigurationsComboBox);
previousConfigurationsComboBox.addChangeHandler(new ChangeHandler() {
@Override
public void onChange(ChangeEvent event) {
updatePanelFromSelectedStoredConfiguration();
}
});
previousConfigurationsComboBox.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
updatePanelFromSelectedStoredConfiguration();
}
});
fillConfigurations();
connectionsGrid.setWidget(0, 0, new Label(stringMessages.swissTimingEvents() + ":"));
connectionsGrid.setWidget(0, 1, previousConfigurationsComboBox);
eventIdBox = new TextBox();
eventIdBox.getElement().getStyle().setWidth(30, Unit.EM);
eventIdBox.setTitle(stringMessages.manage2SailEventIdBoxTooltip());
connectionsGrid.setWidget(1, 0, new Label(stringMessages.manage2SailEventIdBox() + ":"));
connectionsGrid.setWidget(1, 1, eventIdBox);
eventIdBox.addChangeHandler(event -> {
if (eventIdBox.getValue() != "") {
updateUrlFromEventId(eventIdBox.getValue());
}
connectionsTable = new SwissTimingConnectionTableWrapper(userService, sailingService, stringConstants,
errorReporter, true, tableResources, () -> {
});
verticalPanel.add(connectionsTable);
connectionsTable.refreshSwissTimingConnectionList();
// Add button panel
final AccessControlledButtonPanel buttonPanel = new AccessControlledButtonPanel(userService,
SecuredDomainType.TRACKED_RACE);
verticalPanel.add(buttonPanel);
buttonPanel.addUnsecuredAction(stringMessages.refresh(),
() -> connectionsTable.refreshSwissTimingConnectionList());
// Add SwissTiming Connection
buttonPanel.addCreateAction(stringMessages.addSwissTimingConnection(),
() -> new EditSwissTimingConnectionDialog(new SwissTimingConfigurationWithSecurityDTO(),
new DialogCallback<SwissTimingConfigurationWithSecurityDTO>() {
@Override
public void ok(SwissTimingConfigurationWithSecurityDTO editedConnection) {
sailingService.createSwissTimingConfiguration(editedConnection.getName(),
editedConnection.getJsonUrl(), editedConnection.getHostname(),
editedConnection.getPort(), editedConnection.getUpdateURL(),
editedConnection.getUpdateUsername(), editedConnection.getUpdatePassword(),
new MarkedAsyncCallback<Void>(new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter
.reportError("Exception trying to create configuration in DB: "
+ caught.getMessage());
}
@Override
public void onSuccess(Void voidResult) {
connectionsTable.refreshSwissTimingConnectionList();
}
}));
}
@Override
public void cancel() {
}
}, userService, errorReporter).show());
// Remove SwissTiming Connection
final Button removeButton = buttonPanel.addRemoveAction(stringMessages.remove(), () -> {
sailingService.deleteSwissTimingConfiguration(connectionsTable.getSelectionModel().getSelectedObject(),
new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(
"Exception trying to delete configuration in DB: " + caught.getMessage());
}
@Override
public void onSuccess(Void result) {
connectionsTable.refreshSwissTimingConnectionList();
}
});
});
jsonUrlBox = new TextBox();
jsonUrlBox.getElement().getStyle().setWidth(50, Unit.EM);
connectionsGrid.setWidget(2, 0, new Label(stringMessages.manage2SailEventURLBox() + ":"));
connectionsGrid.setWidget(2, 1, jsonUrlBox);
jsonUrlBox.addChangeHandler(event -> {
if (jsonUrlBox.getValue() != "") {
updateEventIdFromUrl(jsonUrlBox.getValue());
}
// List Races in SwissTiming Connection
final Button listRacesButton = buttonPanel.addUnsecuredAction(stringMessages.listRaces(), () -> {
fillRaces(sailingService);
});
hostnameTextbox = new TextBox();
portIntegerbox = new IntegerBox();
connectionsGrid.setWidget(3, 0, new Label(stringConstants.hostname() + ":"));
connectionsGrid.setWidget(3, 1, hostnameTextbox);
connectionsGrid.setWidget(4, 0, new Label(stringMessages.manage2SailPort() + ":"));
connectionsGrid.setWidget(4, 1, portIntegerbox);
updateURLBox = new TextBox();
updateURLBox.getElement().getStyle().setWidth(50, Unit.EM);
connectionsGrid.setWidget(5, 0, new Label(stringMessages.swissTimingUpdateURL() + ":"));
connectionsGrid.setWidget(5, 1, updateURLBox);
updateUsernameBox = new TextBox();
updateUsernameBox.getElement().getStyle().setWidth(50, Unit.EM);
connectionsGrid.setWidget(6, 0, new Label(stringMessages.swissTimingUpdateUsername() + ":"));
connectionsGrid.setWidget(6, 1, updateUsernameBox);
updatePasswordBox = new PasswordTextBox();
updatePasswordBox.getElement().getStyle().setWidth(50, Unit.EM);
connectionsGrid.setWidget(7, 0, new Label(stringMessages.swissTimingUpdatePassword() + ":"));
connectionsGrid.setWidget(7, 1, updatePasswordBox);
Button btnListRaces = new Button(stringConstants.listRaces());
connectionsGrid.setWidget(8, 1, btnListRaces);
btnListRaces.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
fillRaces(sailingService);
}
listRacesButton.setEnabled(false);
removeButton.setEnabled(false);
connectionsTable.getSelectionModel().addSelectionChangeHandler(e -> {
final boolean objectSelected = connectionsTable.getSelectionModel().getSelectedObject() != null;
listRacesButton.setEnabled(objectSelected);
removeButton.setEnabled(objectSelected);
});
HorizontalPanel racesSplitPanel = new HorizontalPanel();
mainPanel.add(racesSplitPanel);
CaptionPanel trackableRacesCaptionPanel = new CaptionPanel(stringConstants.trackableRaces());
racesSplitPanel.add(trackableRacesCaptionPanel);
trackableRacesCaptionPanel.setWidth("50%");
CaptionPanel trackedRacesCaptionPanel = new CaptionPanel(stringConstants.trackedRaces());
racesSplitPanel.add(trackedRacesCaptionPanel);
trackedRacesCaptionPanel.setWidth("50%");
VerticalPanel trackableRacesPanel = new VerticalPanel();
trackableRacesCaptionPanel.setContentWidget(trackableRacesPanel);
trackableRacesCaptionPanel.setStyleName("bold");
VerticalPanel trackedRacesPanel = new VerticalPanel();
trackedRacesPanel.setWidth("100%");
trackedRacesCaptionPanel.setContentWidget(trackedRacesPanel);
trackedRacesCaptionPanel.setStyleName("bold");
// Regatta selection
HorizontalPanel regattaPanel = new HorizontalPanel();
regattaPanel.setSpacing(5);
@@ -195,39 +170,31 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
regattaPanel.add(regattaForTrackingLabel);
regattaPanel.add(getAvailableRegattasListBox());
trackableRacesPanel.add(regattaPanel);
Label lblTrackSettings = new Label(stringConstants.trackSettings());
trackableRacesPanel.add(lblTrackSettings);
final CheckBox trackWindCheckbox = new CheckBox(stringConstants.trackWind());
trackWindCheckbox.setWordWrap(false);
trackWindCheckbox.setValue(true);
trackableRacesPanel.add(trackWindCheckbox);
final CheckBox declinationCheckbox = new CheckBox(stringConstants.declinationCheckbox());
declinationCheckbox.setWordWrap(false);
declinationCheckbox.setValue(true);
trackableRacesPanel.add(declinationCheckbox);
final CheckBox simulateWithStartTimeNow = new CheckBox(stringMessages.simulateAsLiveRace());
simulateWithStartTimeNow.setWordWrap(false);
simulateWithStartTimeNow.setValue(false);
trackableRacesPanel.add(simulateWithStartTimeNow);
final CheckBox useInternalMarkPassingAlgorithmCheckbox = new CheckBox(stringMessages.useInternalAlgorithm());
useInternalMarkPassingAlgorithmCheckbox.setWordWrap(false);
useInternalMarkPassingAlgorithmCheckbox.setValue(Boolean.FALSE);
trackableRacesPanel.add(useInternalMarkPassingAlgorithmCheckbox);
// text box for filtering the cell table
HorizontalPanel filterPanel = new HorizontalPanel();
filterPanel.setSpacing(5);
trackableRacesPanel.add(filterPanel);
Label lblFilterEvents = new Label(stringConstants.filterRaces() + ":");
filterPanel.add(lblFilterEvents);
filterPanel.setCellVerticalAlignment(lblFilterEvents, HasVerticalAlignment.ALIGN_MIDDLE);
raceTable = new FlushableCellTable<SwissTimingRaceRecordDTO>(/* pageSize */10000, tableRes);
raceTable.setWidth("300px");
raceList = new ListDataProvider<SwissTimingRaceRecordDTO>();
@@ -253,7 +220,6 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
return raceTable;
}
};
final EntityIdentityComparator<SwissTimingRaceRecordDTO> entityIdentityComparator = new EntityIdentityComparator<SwissTimingRaceRecordDTO>() {
@Override
public boolean representSameEntity(SwissTimingRaceRecordDTO dto1, SwissTimingRaceRecordDTO dto2) {
@@ -264,21 +230,18 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
return t.raceId.hashCode();
}
};
TextColumn<SwissTimingRaceRecordDTO> raceNameColumn = new TextColumn<SwissTimingRaceRecordDTO>() {
@Override
public String getValue(SwissTimingRaceRecordDTO object) {
return object.getName();
}
};
TextColumn<SwissTimingRaceRecordDTO> regattaNameColumn = new TextColumn<SwissTimingRaceRecordDTO>() {
@Override
public String getValue(SwissTimingRaceRecordDTO object) {
return object.regattaName;
}
};
SelectionCheckboxColumn<SwissTimingRaceRecordDTO> selectionColumn = new SelectionCheckboxColumn<SwissTimingRaceRecordDTO>(
tableRes.cellTableStyle().cellTableCheckboxSelected(),
tableRes.cellTableStyle().cellTableCheckboxDeselected(),
@@ -290,42 +253,36 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
return object.seriesName;
}
};
TextColumn<SwissTimingRaceRecordDTO> raceIdColumn = new TextColumn<SwissTimingRaceRecordDTO>() {
@Override
public String getValue(SwissTimingRaceRecordDTO object) {
return object.raceId;
}
};
TextColumn<SwissTimingRaceRecordDTO> boatClassColumn = new TextColumn<SwissTimingRaceRecordDTO>() {
@Override
public String getValue(SwissTimingRaceRecordDTO object) {
return object.boatClass != null ? object.boatClass : "";
}
};
TextColumn<SwissTimingRaceRecordDTO> genderColumn = new TextColumn<SwissTimingRaceRecordDTO>() {
@Override
public String getValue(SwissTimingRaceRecordDTO object) {
return object.gender != null ? object.gender : "";
}
};
TextColumn<SwissTimingRaceRecordDTO> raceStatusColumn = new TextColumn<SwissTimingRaceRecordDTO>() {
@Override
public String getValue(SwissTimingRaceRecordDTO object) {
return object.raceStatus != null ? object.raceStatus : "";
}
};
TextColumn<SwissTimingRaceRecordDTO> raceStartTimeColumn = new TextColumn<SwissTimingRaceRecordDTO>() {
@Override
public String getValue(SwissTimingRaceRecordDTO object) {
return object.raceStartTime==null?"":dateFormatter.render(object.raceStartTime) + " " + timeFormatter.render(object.raceStartTime);
}
};
raceNameColumn.setSortable(true);
raceStartTimeColumn.setSortable(true);
boatClassColumn.setSortable(true);
@@ -334,8 +291,6 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
raceStatusColumn.setSortable(true);
regattaNameColumn.setSortable(true);
seriesNameColumn.setSortable(true);
raceTable.addColumn(selectionColumn, selectionColumn.getHeader());
raceTable.addColumn(regattaNameColumn, stringConstants.regatta());
raceTable.addColumn(seriesNameColumn, stringConstants.series());
@@ -345,20 +300,16 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
raceTable.addColumn(boatClassColumn, stringConstants.boatClass());
raceTable.addColumn(genderColumn, stringConstants.gender());
raceTable.addColumn(raceStartTimeColumn, stringConstants.startTime());
raceTable.setSelectionModel(selectionColumn.getSelectionModel(), selectionColumn.getSelectionManager());
trackableRacesPanel.add(raceTable);
raceList.addDataDisplay(raceTable);
Handler columnSortHandler = getRaceTableColumnSortHandler(raceList.getList(), regattaNameColumn, seriesNameColumn,
raceNameColumn, raceStartTimeColumn, raceIdColumn, boatClassColumn, genderColumn, raceStatusColumn);
raceTable.addColumnSortHandler(columnSortHandler);
trackedRacesPanel.add(trackedRacesListComposite);
filterPanel.add(filterablePanelEvents);
HorizontalPanel racesButtonPanel = new HorizontalPanel();
trackableRacesPanel.add(racesButtonPanel);
Button btnTrack = new Button(stringConstants.startTracking());
racesButtonPanel.add(btnTrack);
racesButtonPanel.setSpacing(10);
@@ -371,30 +322,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
});
}
/**
* This function tries to infer a valid JsonUrl for any input given that matches the pattern of an event Id from
* M2S. If there is an event id detected the Json Url gets updated and the event Id textbox is filled with the
* detected event Id. The ID pattern is defined in {@link eventIdPattern}.
*/
private void updateUrlFromEventId(String eventIdTextbox) {
if (eventIdTextbox.matches(".*" + eventIdPattern + ".*")) {
final String inferredEventId = eventIdTextbox.replaceFirst(".*(" + eventIdPattern + ").*", "$1");
jsonUrlBox.setValue(
manage2sailBaseAPIUrl + inferredEventId + manage2sailAPIaccessToken + manage2sailUrlAppendix);
eventIdBox.setValue(inferredEventId);
}
}
/**
* Similar to {@link #updateUrlFromEventId} this function tries to extract a M2S event Id by looking at the given
* url in the Json Url Textbox. The value of {@link eventIdBox} is then set to the event ID inferred from the Json Url.
*/
private void updateEventIdFromUrl(String jsonUrlTextBox) {
if (jsonUrlTextBox.matches("http://manage2sail.com/.*" + eventIdPattern + ".*")) {
final String inferredEventId = jsonUrlTextBox.replaceFirst(".*(" + eventIdPattern + ").*", "$1");
eventIdBox.setValue(inferredEventId);
}
}
private ListHandler<SwissTimingRaceRecordDTO> getRaceTableColumnSortHandler(List<SwissTimingRaceRecordDTO> raceRecords,
Column<SwissTimingRaceRecordDTO, ?> regattaNameColumn, Column<SwissTimingRaceRecordDTO, ?> seriesNameColumn,
@@ -456,92 +384,56 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
return result;
}
private void fillConfigurations() {
sailingService.getPreviousSwissTimingConfigurations(new AsyncCallback<List<SwissTimingConfigurationDTO>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Remote Procedure Call getPreviousConfigurations() - Failure: "
+ caught.getMessage());
}
@Override
public void onSuccess(List<SwissTimingConfigurationDTO> result) {
while (previousConfigurationsComboBox.getItemCount() > 0) {
previousConfigurationsComboBox.removeItem(0);
}
Collections.sort(result, (c1, c2) -> c1.getName().compareTo(c2.getName()));
for (SwissTimingConfigurationDTO stConfig : result) {
previousConfigurations.put(stConfig.getName(), stConfig);
previousConfigurationsComboBox.addItem(stConfig.getName());
}
if (!result.isEmpty()) {
updatePanelFromSelectedStoredConfiguration();
}
}
});
}
private void fillRaces(final SailingServiceAsync sailingService) {
final String jsonUrl = jsonUrlBox.getValue();
final String updateURL = updateURLBox.getValue();
final String updateUsername = updateUsernameBox.getValue();
final String updatePassword = updatePasswordBox.getValue();
sailingService.getRacesOfSwissTimingEvent(jsonUrl, new AsyncCallback<SwissTimingEventRecordDTO>() {
@Override
public void onFailure(Throwable caught) {
SwissTimingEventManagementPanel.this.errorReporter.reportError("Error trying to list races: "
+ caught.getMessage());
}
final SwissTimingConfigurationWithSecurityDTO selectedObject = connectionsTable.getSelectionModel()
.getSelectedObject();
sailingService.getRacesOfSwissTimingEvent(selectedObject.getJsonUrl(),
new AsyncCallback<SwissTimingEventRecordDTO>() {
@Override
public void onFailure(Throwable caught) {
SwissTimingEventManagementPanel.this.errorReporter
.reportError("Error trying to list races: " + caught.getMessage());
}
@Override
public void onSuccess(final SwissTimingEventRecordDTO result) {
availableSwissTimingRaces.clear();
if (result != null) {
availableSwissTimingRaces.addAll(result.races);
}
raceList.getList().clear();
raceList.getList().addAll(availableSwissTimingRaces);
filterablePanelEvents.getTextBox().setText(null);
filterablePanelEvents.updateAll(result.races);
// store a successful configuration in the database for later retrieval
final String hostname = result.trackingDataHost;
final Integer port = result.trackingDataPort;
final String configName = result.eventName;
sailingService.storeSwissTimingConfiguration(configName, jsonUrl, hostname, port, updateURL, updateUsername, updatePassword,
new AsyncCallback<Void>() {
@Override
public void onSuccess(final SwissTimingEventRecordDTO result) {
availableSwissTimingRaces.clear();
if (result != null) {
availableSwissTimingRaces.addAll(result.races);
}
raceList.getList().clear();
raceList.getList().addAll(availableSwissTimingRaces);
filterablePanelEvents.getTextBox().setText(null);
filterablePanelEvents.updateAll(result.races);
// store a successful configuration in the database for later retrieval
final SwissTimingConfigurationWithSecurityDTO updatedDTO = new SwissTimingConfigurationWithSecurityDTO(
selectedObject, result.trackingDataHost, result.trackingDataPort, result.eventName);
sailingService.updateSwissTimingConfiguration(updatedDTO, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Exception trying to store configuration in DB: "
+ caught.getMessage());
errorReporter.reportError(
"Exception trying to update configuration in DB: " + caught.getMessage());
}
@Override
public void onSuccess(Void voidResult) {
// refresh list of previous configurations
SwissTimingConfigurationDTO stConfig = new SwissTimingConfigurationDTO(configName, jsonUrl,
hostname, port, updateURL, updateUsername, updatePassword);
if (previousConfigurations.put(stConfig.getName(), stConfig) == null) {
previousConfigurationsComboBox.addItem(stConfig.getName());
}
for (int i=0; i<previousConfigurationsComboBox.getItemCount(); i++) {
if (previousConfigurationsComboBox.getValue(i).equals(stConfig.getName())) {
previousConfigurationsComboBox.setSelectedIndex(i);
break;
}
}
updatePanelFromSelectedStoredConfiguration();
connectionsTable.refreshSwissTimingConnectionList();
}
});
}
});
}
});
}
private void trackSelectedRaces(boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm) {
final String hostname = hostnameTextbox.getValue();
final Integer port = portIntegerbox.getValue();
final String updateURL = updateURLBox.getValue();
final String updateUsername = updateUsernameBox.getValue();
final String updatePassword = updatePasswordBox.getValue();
final SwissTimingConfigurationWithSecurityDTO selectedObject = connectionsTable.getSelectionModel()
.getSelectedObject();
final String hostname = selectedObject.getHostname();
final Integer port = selectedObject.getPort();
final String updateURL = selectedObject.getUpdateURL();
final String updateUsername = selectedObject.getUpdateUsername();
final String updatePassword = selectedObject.getUpdatePassword();
final List<SwissTimingRaceRecordDTO> selectedRaces = new ArrayList<SwissTimingRaceRecordDTO>();
for (final SwissTimingRaceRecordDTO race : this.raceList.getList()) {
if (raceTable.getSelectionModel().isSelected(race)) {
@@ -573,20 +465,4 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
});
}
}
private void updatePanelFromSelectedStoredConfiguration() {
if (previousConfigurationsComboBox.getSelectedIndex() >= 0) {
SwissTimingConfigurationDTO stConfig = previousConfigurations.get(previousConfigurationsComboBox
.getItemText(previousConfigurationsComboBox.getSelectedIndex()));
if (stConfig != null) {
hostnameTextbox.setValue(stConfig.getHostname());
portIntegerbox.setValue(stConfig.getPort());
jsonUrlBox.setValue(stConfig.getJsonURL());
updateURLBox.setValue(stConfig.getUpdateURL());
updateUsernameBox.setValue(stConfig.getUpdateUsername());
updatePasswordBox.setValue(stConfig.getUpdatePassword());
}
}
}
}
@@ -2,16 +2,10 @@ package com.sap.sailing.gwt.ui.adminconsole;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.cellview.client.AbstractCellTable;
@@ -24,30 +18,34 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CaptionPanel;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.view.client.ListDataProvider;
import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.RegattaName;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sailing.gwt.ui.adminconsole.swisstiming.EditSwissTimingArchivedConnectionDialog;
import com.sap.sailing.gwt.ui.adminconsole.swisstiming.SwissTimingArchivedConnectionTableWrapper;
import com.sap.sailing.gwt.ui.client.RegattaRefresher;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingArchiveConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingArchiveConfigurationWithSecurityDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingReplayRaceDTO;
import com.sap.sse.common.util.NaturalComparator;
import com.sap.sse.gwt.adminconsole.AdminConsoleTableResources;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.gwt.client.celltable.BaseCelltable;
import com.sap.sse.gwt.client.celltable.CellTableWithCheckboxResources;
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel;
import com.sap.sse.security.ui.client.UserService;
import com.sap.sse.security.ui.client.component.AccessControlledButtonPanel;
/**
* Allows the user to start and stop tracking of events, regattas and races using the SwissTiming connector.
@@ -61,59 +59,42 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane
private final LabeledAbstractFilterablePanel<SwissTimingReplayRaceDTO> filterablePanelEvents;
private final ListDataProvider<SwissTimingReplayRaceDTO> raceList;
private final CellTable<SwissTimingReplayRaceDTO> raceTable;
private final Map<String, SwissTimingArchiveConfigurationDTO> previousConfigurations;
private final ListBox previousConfigurationsComboBox;
private final TextBox jsonUrlBox;
private final Grid grid;
private final List<SwissTimingReplayRaceDTO> availableSwissTimingRaces;
private final SwissTimingArchivedConnectionTableWrapper connectionsTable;
public SwissTimingReplayConnectorPanel(final SailingServiceAsync sailingService, UserService userService,
ErrorReporter errorReporter, RegattaRefresher regattaRefresher, StringMessages stringMessages) {
ErrorReporter errorReporter, RegattaRefresher regattaRefresher, StringMessages stringMessages, CellTableWithCheckboxResources tableResources) {
super(sailingService, userService, regattaRefresher, errorReporter, true, stringMessages);
this.errorReporter = errorReporter;
availableSwissTimingRaces = new ArrayList<SwissTimingReplayRaceDTO>();
previousConfigurationsComboBox = new ListBox();
previousConfigurationsComboBox.addChangeHandler(new ChangeHandler() {
@Override
public void onChange(ChangeEvent event) {
updateJsonUrlFromSelectedPreviousConfiguration();
}
});
previousConfigurations = new HashMap<String, SwissTimingArchiveConfigurationDTO>();
getConnectionHistory();
jsonUrlBox = new TextBox();
jsonUrlBox.getElement().getStyle().setWidth(50, Unit.EM);
VerticalPanel mainPanel = new VerticalPanel();
// setup UI
final VerticalPanel mainPanel = new VerticalPanel();
this.setWidget(mainPanel);
mainPanel.setWidth("100%");
CaptionPanel captionPanelConnections = new CaptionPanel(stringMessages.connections());
final CaptionPanel captionPanelConnections = new CaptionPanel(stringMessages.connections());
mainPanel.add(captionPanelConnections);
VerticalPanel verticalPanel = new VerticalPanel();
final VerticalPanel verticalPanel = new VerticalPanel();
captionPanelConnections.setContentWidget(verticalPanel);
captionPanelConnections.setStyleName("bold");
grid = new Grid(3, 2);
verticalPanel.add(grid);
grid.setWidget(0, 0, new Label(stringMessages.swissTimingEvents() + ":"));
grid.setWidget(0, 1, previousConfigurationsComboBox);
grid.setWidget(1, 0, new Label(stringMessages.jsonUrl() + ":"));
grid.setWidget(1, 1, jsonUrlBox);
// add connections table
connectionsTable = new SwissTimingArchivedConnectionTableWrapper(userService, sailingService, stringMessages,
errorReporter, true, tableResources, () -> {
});
connectionsTable.refreshConnectionList();
verticalPanel.add(connectionsTable);
Button btnListRaces = new Button(stringMessages.listRaces());
grid.setWidget(2, 1, btnListRaces);
btnListRaces.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
fillRaces(sailingService);
}
});
// create button UI
final AccessControlledButtonPanel buttonPanel = createButtonPanel(sailingService, userService, errorReporter,
stringMessages);
verticalPanel.add(buttonPanel);
// Table
TextColumn<SwissTimingReplayRaceDTO> regattaNameColumn = new TextColumn<SwissTimingReplayRaceDTO>() {
@Override
public String getValue(SwissTimingReplayRaceDTO object) {
@@ -265,6 +246,69 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane
}
private AccessControlledButtonPanel createButtonPanel(final SailingServiceAsync sailingService,
UserService userService, ErrorReporter errorReporter, StringMessages stringMessages) {
final AccessControlledButtonPanel buttonPanel = new AccessControlledButtonPanel(userService,
SecuredDomainType.SWISS_TIMING_ARCHIVE_ACCOUNT);
// Refresh action
buttonPanel.addUnsecuredAction(stringMessages.refresh(), () -> connectionsTable.refreshConnectionList());
// Create action
buttonPanel.addCreateAction(stringMessages.addSwissTimingAchivedConnection(),
() -> new EditSwissTimingArchivedConnectionDialog(new SwissTimingArchiveConfigurationWithSecurityDTO(),
new DialogCallback<SwissTimingArchiveConfigurationWithSecurityDTO>() {
@Override
public void ok(SwissTimingArchiveConfigurationWithSecurityDTO editedConnection) {
sailingService.createSwissTimingArchiveConfiguration(editedConnection.getJsonUrl(),
new MarkedAsyncCallback<Void>(new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter
.reportError("Exception trying to create configuration in DB: "
+ caught.getMessage());
}
@Override
public void onSuccess(Void voidResult) {
connectionsTable.refreshConnectionList();
}
}));
}
@Override
public void cancel() {
}
}, userService, errorReporter).show());
// Remove action
final Button removeButton = buttonPanel.addRemoveAction(stringMessages.remove(), () -> {
sailingService.deleteSwissTimingArchiveConfiguration(
connectionsTable.getSelectionModel().getSelectedObject(), new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(
"Exception trying to delete configuration in DB: " + caught.getMessage());
}
@Override
public void onSuccess(Void result) {
connectionsTable.refreshConnectionList();
}
});
});
// List Race action
final Button listRacesButton = buttonPanel.addUnsecuredAction(stringMessages.listRaces(), () -> {
fillRaces(sailingService);
});
listRacesButton.setEnabled(false);
removeButton.setEnabled(false);
// add change handlers to enable and disable List Races and Remove
connectionsTable.getSelectionModel().addSelectionChangeHandler(e -> {
final boolean objectSelected = connectionsTable.getSelectionModel().getSelectedObject() != null;
listRacesButton.setEnabled(objectSelected);
removeButton.setEnabled(objectSelected);
});
return buttonPanel;
}
private String getBoatClassNamesAsString(SwissTimingReplayRaceDTO object) {
return object.boat_class;
}
@@ -295,32 +339,12 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane
return result;
}
private void getConnectionHistory() {
sailingService.getPreviousSwissTimingArchiveConfigurations(new AsyncCallback<List<SwissTimingArchiveConfigurationDTO>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Remote Procedure Call getPreviousConfigurations() - Failure: "
+ caught.getMessage());
}
@Override
public void onSuccess(List<SwissTimingArchiveConfigurationDTO> result) {
previousConfigurationsComboBox.clear();
previousConfigurations.clear();
Collections.sort(result, (c1, c2) -> c1.getJsonUrl().compareTo(c2.getJsonUrl()));
for (SwissTimingArchiveConfigurationDTO configEntry : result) {
String name = configEntry.getJsonUrl();
previousConfigurations.put(name, configEntry);
previousConfigurationsComboBox.addItem(name);
}
updateJsonUrlFromSelectedPreviousConfiguration();
}
});
}
private void fillRaces(final SailingServiceAsync sailingService) {
final String swissTimingJsonUrl = jsonUrlBox.getValue();
sailingService.listSwissTiminigReplayRaces(swissTimingJsonUrl, new AsyncCallback<List<SwissTimingReplayRaceDTO>>() {
final SwissTimingArchiveConfigurationWithSecurityDTO selectedObject = connectionsTable.getSelectionModel()
.getSelectedObject();
if (selectedObject != null) {
sailingService.listSwissTiminigReplayRaces(selectedObject.getJsonUrl(),
new AsyncCallback<List<SwissTimingReplayRaceDTO>>() {
@Override
public void onFailure(Throwable caught) {
SwissTimingReplayConnectorPanel.this.errorReporter.reportError("Error trying to list SwissTiming races: "
@@ -335,26 +359,9 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane
raceList.getList().addAll(availableSwissTimingRaces);
filterablePanelEvents.getTextBox().setText(null);
filterablePanelEvents.updateAll(races);
// store a successful configuration in the database for later retrieval
sailingService.storeSwissTimingArchiveConfiguration(swissTimingJsonUrl,
new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Exception trying to store configuration in DB: "
+ caught.getMessage());
}
@Override
public void onSuccess(Void voidResult) {
// refresh list of previous configurations
SwissTimingArchiveConfigurationDTO stConfig = new SwissTimingArchiveConfigurationDTO(swissTimingJsonUrl);
if (previousConfigurations.put(stConfig.getJsonUrl(), stConfig) == null) {
previousConfigurationsComboBox.addItem(stConfig.getJsonUrl());
}
}
});
}
});
}
}
private void trackSelectedRaces(boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm) {
@@ -387,16 +394,4 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane
});
}
}
private void updateJsonUrlFromSelectedPreviousConfiguration() {
int selectedIndex = previousConfigurationsComboBox.getSelectedIndex();
String selectedConfiguration;
if (selectedIndex >= 0) {
selectedConfiguration = previousConfigurationsComboBox.getItemText(selectedIndex);
} else {
selectedConfiguration = null;
}
jsonUrlBox.setText(selectedConfiguration);
}
}
@@ -1,17 +1,12 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.cellview.client.AbstractCellTable;
@@ -28,31 +23,34 @@ import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTMLTable.ColumnFormatter;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.ListDataProvider;
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.security.SecuredDomainType;
import com.sap.sailing.gwt.ui.adminconsole.tractrac.EditTracTracConnectionDialog;
import com.sap.sailing.gwt.ui.adminconsole.tractrac.TracTracConnectionTableWrapper;
import com.sap.sailing.gwt.ui.client.RegattaRefresher;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.TracTracConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.TracTracConfigurationWithSecurityDTO;
import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO;
import com.sap.sse.common.Duration;
import com.sap.sse.common.util.NaturalComparator;
import com.sap.sse.gwt.adminconsole.AdminConsoleTableResources;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.gwt.client.celltable.CellTableWithCheckboxResources;
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
import com.sap.sse.gwt.client.celltable.FlushableCellTable;
import com.sap.sse.gwt.client.celltable.SelectionCheckboxColumn;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel;
import com.sap.sse.security.ui.client.UserService;
import com.sap.sse.security.ui.client.component.AccessControlledButtonPanel;
/**
* Allows the user to start and stop tracking of events, regattas and races using the TracTrac connector. In particular,
@@ -70,30 +68,27 @@ import com.sap.sse.security.ui.client.UserService;
public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
private final ErrorReporter errorReporter;
private final Map<String, TracTracConfigurationDTO> previousConfigurations;
private final List<TracTracRaceRecordDTO> availableTracTracRaces;
private final ListDataProvider<TracTracRaceRecordDTO> raceList;
private ListBox connectionsHistoryListBox;
private TextBox storedURITextBox;
private TextBox liveURITextBox;
private TextBox jsonURLTextBox;
private TextBox tracTracUpdateURITextBox;
private TextBox tractracUsernameTextBox;
private PasswordTextBox tractracPasswordTextBox;
private TracTracConnectionTableWrapper connectionsTable;
private Label loadingMessageLabel;
private LabeledAbstractFilterablePanel<TracTracRaceRecordDTO> racesFilterablePanel;
private FlushableCellTable<TracTracRaceRecordDTO> racesTable;
private final UserService userService;
private final CellTableWithCheckboxResources tableResources;
public TracTracEventManagementPanel(final SailingServiceAsync sailingService, UserService userService,
ErrorReporter errorReporter, RegattaRefresher regattaRefresher, StringMessages stringMessages) {
ErrorReporter errorReporter, RegattaRefresher regattaRefresher, StringMessages stringMessages,
final CellTableWithCheckboxResources tableResources) {
super(sailingService, userService, regattaRefresher, errorReporter, true, stringMessages);
this.userService = userService;
this.errorReporter = errorReporter;
this.previousConfigurations = new HashMap<String, TracTracConfigurationDTO>();
this.tableResources = tableResources;
this.availableTracTracRaces = new ArrayList<TracTracRaceRecordDTO>();
this.raceList = new ListDataProvider<TracTracRaceRecordDTO>();
this.setWidget(createContent());
@@ -111,8 +106,6 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
mainPanel.add(racesPanel);
fillConfigurations();
return mainPanel;
}
@@ -120,113 +113,80 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
CaptionPanel connectionsPanel = new CaptionPanel("TracTrac " + stringMessages.connections());
connectionsPanel.ensureDebugId("ConnectionsSection");
connectionsPanel.setStyleName("bold");
Grid grid = new Grid(10, 3);
connectionsPanel.setContentWidget(grid);
// Existing TracTrac connections
Label connectionsHistoryLabel = new Label("TracTrac " + stringMessages.connections() + ":");
connectionsHistoryListBox = new ListBox();
connectionsHistoryListBox.ensureDebugId("ConnectionHistory");
connectionsHistoryListBox.addChangeHandler(new ChangeHandler() {
@Override
public void onChange(ChangeEvent event) {
updatePanelFromSelectedStoredConfiguration();
}
});
connectionsHistoryListBox.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
// TODO: This leads to a strange behavior (values are updated by opening the drop down) but at the
// moment there is no other way to "reset" the values to the selected value, if the selection does
// not change since there is no close event or something similar.
updatePanelFromSelectedStoredConfiguration();
}
});
grid.setWidget(0, 0, connectionsHistoryLabel);
grid.setWidget(0, 1, connectionsHistoryListBox);
// URIs (Live and Stored)
grid.setWidget(1, 0, new Label(stringMessages.details() + ":"));
Label liveURILabel = new Label(stringMessages.liveUri() + ":");
liveURILabel.setTitle(stringMessages.leaveEmptyForDefault());
liveURITextBox = new TextBox();
liveURITextBox.ensureDebugId("LiveURITextBox");
liveURITextBox.setVisibleLength(40);
liveURITextBox.setTitle(stringMessages.leaveEmptyForDefault());
grid.setWidget(2, 0, liveURILabel);
grid.setWidget(2, 1, liveURITextBox);
Label storedURILabel = new Label(stringMessages.storedUri() + ":");
storedURILabel.setTitle(stringMessages.leaveEmptyForDefault());
storedURITextBox = new TextBox();
storedURITextBox.ensureDebugId("StoredURITextBox");
storedURITextBox.setVisibleLength(40);
storedURITextBox.setTitle(stringMessages.leaveEmptyForDefault());
grid.setWidget(3, 0, storedURILabel);
grid.setWidget(3, 1, storedURITextBox);
// JSON URL
Label jsonURLLabel = new Label(stringMessages.jsonUrl() + ":");
jsonURLTextBox = new TextBox();
jsonURLTextBox.ensureDebugId("JsonURLTextBox");
jsonURLTextBox.setVisibleLength(100);
grid.setWidget(4, 0, jsonURLLabel);
grid.setWidget(4, 1, jsonURLTextBox);
// Course design Update URL
Label tracTracUpdateURLLabel = new Label(stringMessages.tracTracUpdateUrl() + ":");
tracTracUpdateURITextBox = new TextBox();
tracTracUpdateURITextBox.ensureDebugId("TracTracUpdateURITextBox");
tracTracUpdateURITextBox.setVisibleLength(100);
grid.setWidget(5, 0, tracTracUpdateURLLabel);
grid.setWidget(5, 1, tracTracUpdateURITextBox);
// TracTrac Username
tractracUsernameTextBox = new TextBox();
tractracUsernameTextBox.ensureDebugId("TracTracUsernameTextBox");
tractracUsernameTextBox.setVisibleLength(40);
grid.setWidget(6, 0, new Label(stringMessages.tractracUsername() + ":"));
grid.setWidget(6, 1, tractracUsernameTextBox);
// TracTrac Password
tractracPasswordTextBox = new PasswordTextBox();
tractracPasswordTextBox.ensureDebugId("TracTracPasswordTextBox");
tractracPasswordTextBox.setVisibleLength(40);
grid.setWidget(7, 0, new Label(stringMessages.tractracPassword() + ":"));
grid.setWidget(7, 1, tractracPasswordTextBox);
grid.setWidget(8, 0, new Label(stringMessages.racesWithHiddenState() + ":"));
VerticalPanel tableAndConfigurationPanel = new VerticalPanel();
connectionsTable = new TracTracConnectionTableWrapper(userService, sailingService, stringMessages,
errorReporter, true, tableResources, () -> {});
connectionsTable.refreshTracTracConnectionList();
connectionsPanel.setContentWidget(tableAndConfigurationPanel);
final Grid grid = new Grid(1, 2);
grid.setWidget(0, 0, new Label(stringMessages.racesWithHiddenState() + ":"));
final CheckBox showHiddenRacesCheckbox = new CheckBox(stringMessages.show());
showHiddenRacesCheckbox.ensureDebugId("ShowHiddenRacesCheckBox");
grid.setWidget(8, 1, showHiddenRacesCheckbox);
grid.setWidget(0, 1, showHiddenRacesCheckbox);
tableAndConfigurationPanel.add(connectionsTable);
tableAndConfigurationPanel.add(grid);
// Add TracTrac Connection
final AccessControlledButtonPanel buttonPanel = new AccessControlledButtonPanel(userService, SecuredDomainType.TRACKED_RACE);
tableAndConfigurationPanel.add(buttonPanel);
buttonPanel.addUnsecuredAction(stringMessages.refresh(), () -> connectionsTable.refreshTracTracConnectionList());
Button addCreateAction = buttonPanel.addCreateAction(stringMessages.addTracTracConnection(),
() -> new EditTracTracConnectionDialog(new TracTracConfigurationWithSecurityDTO(),
new DialogCallback<TracTracConfigurationWithSecurityDTO>() {
@Override
public void ok(TracTracConfigurationWithSecurityDTO editedConnection) {
sailingService.createTracTracConfiguration(editedConnection.getName(),
editedConnection.getJsonUrl(), editedConnection.getLiveDataURI(),
editedConnection.getStoredDataURI(),
editedConnection.getCourseDesignUpdateURI(),
editedConnection.getTracTracUsername(), editedConnection.getTracTracPassword(),
new MarkedAsyncCallback<Void>(new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
reportError("Exception trying to create configuration in DB: "
+ caught.getMessage());
}
// List Races
Button listRacesButton = new Button(stringMessages.listRaces());
listRacesButton.ensureDebugId("ListRacesButton");
listRacesButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
loadingMessageLabel.setText(stringMessages.loading());
fillRaces(sailingService, showHiddenRacesCheckbox.getValue());
}
@Override
public void onSuccess(Void voidResult) {
connectionsTable.refreshTracTracConnectionList();
}
}));
}
@Override
public void cancel() {
}
}, userService, errorReporter).show());
addCreateAction.ensureDebugId("AddConnectionButton");
final Button removeButton = buttonPanel.addRemoveAction(stringMessages.remove(), () -> {
sailingService.deleteTracTracConfiguration(connectionsTable.getSelectionModel().getSelectedObject(),
new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(
"Exception trying to delete configuration in DB: " + caught.getMessage());
}
@Override
public void onSuccess(Void result) {
connectionsTable.refreshTracTracConnectionList();
}
});
});
grid.setWidget(9, 1, listRacesButton);
loadingMessageLabel = new Label();
grid.setWidget(9, 2, loadingMessageLabel);
final Button listRacesButton = buttonPanel.addUnsecuredAction(stringMessages.listRaces(), () -> {
loadingMessageLabel.setText(stringMessages.loading());
fillRaces(sailingService, showHiddenRacesCheckbox.getValue());
});
listRacesButton.ensureDebugId("ListRacesButton");
listRacesButton.setEnabled(false);
removeButton.setEnabled(false);
buttonPanel.addUnsecuredWidget(loadingMessageLabel);
connectionsTable.getSelectionModel().addSelectionChangeHandler(e -> {
final boolean objectSelected = connectionsTable.getSelectionModel().getSelectedObject() != null;
listRacesButton.setEnabled(objectSelected);
removeButton.setEnabled(objectSelected);
});
return connectionsPanel;
}
@@ -386,7 +346,10 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
raceList.addDataDisplay(racesTable);
layoutTable.setWidget(++row, 0, racesTable);
cellFormatter.setColSpan(row, 0, 2);
Button startTrackingButton = new Button(stringMessages.startTracking());
final Button startTrackingButton = new Button(stringMessages.startTracking());
startTrackingButton.setEnabled(false);
connectionsTable.getSelectionModel().addSelectionChangeHandler(
e -> startTrackingButton.setEnabled(connectionsTable.getSelectionModel().getSelectedObject() != null));
startTrackingButton.ensureDebugId("StartTrackingButton");
startTrackingButton.addClickHandler(new ClickHandler() {
@Override
@@ -480,40 +443,13 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
return result;
}
private void fillConfigurations() {
this.sailingService.getPreviousTracTracConfigurations(new MarkedAsyncCallback<List<TracTracConfigurationDTO>>(
new AsyncCallback<List<TracTracConfigurationDTO>>() {
@Override
public void onFailure(Throwable caught) {
reportError("Remote Procedure Call getPreviousConfigurations() - Failure: " + caught.getMessage());
}
@Override
public void onSuccess(List<TracTracConfigurationDTO> result) {
TracTracEventManagementPanel.this.previousConfigurations.clear();
TracTracEventManagementPanel.this.connectionsHistoryListBox.clear();
Collections.sort(result, (c1, c2) -> c1.name.compareTo(c2.name));
for (TracTracConfigurationDTO config : result) {
TracTracEventManagementPanel.this.previousConfigurations.put(config.name, config);
TracTracEventManagementPanel.this.connectionsHistoryListBox.addItem(config.name, config.name);
}
if (!result.isEmpty()) {
updatePanelFromSelectedStoredConfiguration();
}
}
}));
}
private void fillRaces(final SailingServiceAsync sailingService, boolean listHiddenRaces) {
final String jsonURL = jsonURLTextBox.getValue();
final String liveDataURI = liveURITextBox.getValue();
final String storedDataURI = storedURITextBox.getValue();
final String courseDesignUpdateURI = tracTracUpdateURITextBox.getValue();
final String tractracUsername = tractracUsernameTextBox.getValue();
final String tractracPassword = tractracPasswordTextBox.getValue();
final TracTracConfigurationWithSecurityDTO selectedConnection = connectionsTable.getSelectionModel()
.getSelectedObject();
if (selectedConnection != null) {
sailingService.listTracTracRacesInEvent(jsonURL, listHiddenRaces, new MarkedAsyncCallback<com.sap.sse.common.Util.Pair<String, List<TracTracRaceRecordDTO>>>(
sailingService.listTracTracRacesInEvent(selectedConnection.getJsonUrl(), listHiddenRaces,
new MarkedAsyncCallback<com.sap.sse.common.Util.Pair<String, List<TracTracRaceRecordDTO>>>(
new AsyncCallback<com.sap.sse.common.Util.Pair<String, List<TracTracRaceRecordDTO>>>() {
@Override
public void onFailure(Throwable caught) {
@@ -525,7 +461,9 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
public void onSuccess(final com.sap.sse.common.Util.Pair<String, List<TracTracRaceRecordDTO>> result) {
loadingMessageLabel.setText("Building resultset and saving configuration...");
TracTracEventManagementPanel.this.availableTracTracRaces.clear();
final String eventName = result.getA();
final TracTracConfigurationWithSecurityDTO updatedConnection = new TracTracConfigurationWithSecurityDTO(
selectedConnection,
result.getA());
final List<TracTracRaceRecordDTO> eventRaces = result.getB();
if (eventRaces != null) {
TracTracEventManagementPanel.this.availableTracTracRaces.addAll(eventRaces);
@@ -540,8 +478,8 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
loadingMessageLabel.setText("");
// store a successful configuration in the database for later retrieval
sailingService.storeTracTracConfiguration(eventName, jsonURL, liveDataURI, storedDataURI,
courseDesignUpdateURI, tractracUsername, tractracPassword, new MarkedAsyncCallback<Void>(
sailingService.updateTracTracConfiguration(updatedConnection,
new MarkedAsyncCallback<Void>(
new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
@@ -550,25 +488,22 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
@Override
public void onSuccess(Void voidResult) {
// refresh list of previous configurations
TracTracConfigurationDTO config = new TracTracConfigurationDTO(eventName, jsonURL,
liveDataURI, storedDataURI, courseDesignUpdateURI, tractracUsername, tractracPassword);
if (TracTracEventManagementPanel.this.previousConfigurations.put(config.name, config) == null) {
TracTracEventManagementPanel.this.connectionsHistoryListBox.addItem(config.name, config.name);
}
connectionsTable.refreshTracTracConnectionList();
}
}));
}
}));
}
}
private void trackSelectedRaces(boolean trackWind, boolean correctWind, final Duration offsetToStartTimeOfSimulatedRace, boolean ignoreTracTracMarkPassings) {
String liveURI = liveURITextBox.getValue();
String storedURI = storedURITextBox.getValue();
String courseDesignUpdateURI = tracTracUpdateURITextBox.getValue();
String tractracUsername = tractracUsernameTextBox.getValue();
String tractracPassword = tractracPasswordTextBox.getValue();
final TracTracConfigurationWithSecurityDTO selectedConnection = connectionsTable.getSelectionModel()
.getSelectedObject();
String liveURI = selectedConnection.getLiveDataURI();
String storedURI = selectedConnection.getStoredDataURI();
String courseDesignUpdateURI = selectedConnection.getCourseDesignUpdateURI();
String tractracUsername = selectedConnection.getTracTracUsername();
String tractracPassword = selectedConnection.getTracTracPassword();
RegattaDTO selectedRegatta = getSelectedRegatta(); // null meaning "Default Regatta" selection
RegattaIdentifier regattaIdentifier = null;
if (selectedRegatta != null) {
@@ -603,19 +538,9 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
}));
}
}
private void updatePanelFromSelectedStoredConfiguration() {
int index = connectionsHistoryListBox.getSelectedIndex();
if (index != -1) {
String configurationKey = connectionsHistoryListBox.getValue(index);
TracTracConfigurationDTO config = previousConfigurations.get(configurationKey);
jsonURLTextBox.setValue(config.jsonURL);
liveURITextBox.setValue(config.liveDataURI);
storedURITextBox.setValue(config.storedDataURI);
tracTracUpdateURITextBox.setValue(config.courseDesignUpdateURI);
tractracUsernameTextBox.setValue(config.tractracUsername);
tractracPasswordTextBox.setValue(config.tractracPassword);
}
}
public void refreshTracTracConnectors() {
connectionsTable.refreshTracTracConnectionList();
raceList.getList().clear();
}
}
@@ -0,0 +1,91 @@
package com.sap.sailing.gwt.ui.adminconsole.swisstiming;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.SwissTimingArchiveConfigurationWithSecurityDTO;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
import com.sap.sse.security.ui.client.UserService;
/**
* Edits a {@link SwissTimingArchiveConfigurationWithSecurityDTO} object. Can be accessed from
* {@link SwissTimingArchivedConnectionTableWrapper}
*/
public class EditSwissTimingArchivedConnectionDialog
extends DataEntryDialog<SwissTimingArchiveConfigurationWithSecurityDTO> {
private static final StringMessages stringMessages = StringMessages.INSTANCE;
private Grid grid;
private TextBox jsonURLTextBox;
private final SwissTimingArchiveConfigurationWithSecurityDTO dtoToEdit;
private static class EmptyTextValidator implements Validator<SwissTimingArchiveConfigurationWithSecurityDTO> {
@Override
public String getErrorMessage(SwissTimingArchiveConfigurationWithSecurityDTO valueToValidate) {
final String result;
if (valueToValidate.getJsonUrl() == null || valueToValidate.getJsonUrl().trim().isEmpty()) {
result = stringMessages.pleaseEnterNonEmptyUrl();
} else {
result = null;
}
return result;
}
}
/**
* The class creates the UI-dialog to type in the Data about a the selected swiss timing account.
*
* @param userToEdit
* The 'userToEdit' parameter contains the user which should be changed or initialized.
*/
public EditSwissTimingArchivedConnectionDialog(final SwissTimingArchiveConfigurationWithSecurityDTO dtoToEdit,
final DialogCallback<SwissTimingArchiveConfigurationWithSecurityDTO> callback,
final UserService userService,
final ErrorReporter errorReporter) {
super(stringMessages.editSwissTimingAchivedConnection(), null, stringMessages.ok(), stringMessages.cancel(),
/* validator */ new EmptyTextValidator(), /* animationEnabled */true, callback);
this.dtoToEdit = dtoToEdit;
this.ensureDebugId("EditSwissTimingArchivedConnectionDialog");
createUi();
setData(dtoToEdit);
}
private void setData(final SwissTimingArchiveConfigurationWithSecurityDTO dtotoEdit) {
jsonURLTextBox.setText(dtotoEdit.getJsonUrl());
validateAndUpdate();
}
private void createUi() {
grid = new Grid(2, 2);
grid.setWidget(0, 0, new Label(stringMessages.details() + ":"));
// JSON URL
Label jsonURLLabel = new Label(stringMessages.jsonUrl() + ":");
jsonURLTextBox = createTextBox("");
jsonURLTextBox.ensureDebugId("JsonURLTextBox");
jsonURLTextBox.setVisibleLength(100);
// validation: User should not create empty connections
grid.setWidget(1, 0, jsonURLLabel);
grid.setWidget(1, 1, jsonURLTextBox);
}
@Override
protected FocusWidget getInitialFocusWidget() {
return jsonURLTextBox;
}
@Override
protected SwissTimingArchiveConfigurationWithSecurityDTO getResult() {
final String jsonURL = jsonURLTextBox.getValue();
return new SwissTimingArchiveConfigurationWithSecurityDTO(jsonURL, dtoToEdit.getCreatorName());
}
@Override
protected Widget getAdditionalWidget() {
return grid;
}
}
@@ -0,0 +1,183 @@
package com.sap.sailing.gwt.ui.adminconsole.swisstiming;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.SwissTimingConfigurationWithSecurityDTO;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
import com.sap.sse.security.ui.client.UserService;
/**
* Edits a {@link SwissTimingConfigurationWithSecurityDTO} object. Can be accessed from
* {@link SwissTimingConnectionTableWrapper}. The Manage2Sail event ID and the complete URL
* field update each other mutually upon manual update.
*/
public class EditSwissTimingConnectionDialog extends DataEntryDialog<SwissTimingConfigurationWithSecurityDTO> {
private static final StringMessages stringMessages = StringMessages.INSTANCE;
private Grid grid;
private TextBox manage2SailEventIdTextBox;
private TextBox manage2SailEventUrlJsonTextBox;
private TextBox hostnameTextBox;
private TextBox portTextBox;
private TextBox updateUrlTextBox;
private TextBox updateUsernameTextBox;
private PasswordTextBox updatePasswordTextBox;
private final SwissTimingConfigurationWithSecurityDTO dtoToEdit;
private static class EmptyFieldValidator implements Validator<SwissTimingConfigurationWithSecurityDTO> {
@Override
public String getErrorMessage(SwissTimingConfigurationWithSecurityDTO valueToValidate) {
final String result;
if (valueToValidate.getJsonUrl() == null || valueToValidate.getJsonUrl().trim().isEmpty()) {
result = stringMessages.pleaseEnterNonEmptyUrl();
} else {
result = null;
}
return result;
}
}
/**
* The class creates the UI-dialog to type in the Data about a the selected swiss timing account.
*
* @param userToEdit
* The 'userToEdit' parameter contains the user which should be changed or initialized.
*/
public EditSwissTimingConnectionDialog(final SwissTimingConfigurationWithSecurityDTO dtotoEdit,
final DialogCallback<SwissTimingConfigurationWithSecurityDTO> callback, final UserService userService,
final ErrorReporter errorReporter) {
super(stringMessages.editSwissTimingConnections(), null, stringMessages.ok(), stringMessages.cancel(),
/* validator */ new EmptyFieldValidator(),
/* animationEnabled */true, callback);
this.dtoToEdit = dtotoEdit;
this.ensureDebugId("EditTracTracConnectionDialog");
createUi();
setData(dtotoEdit);
}
private void setData(final SwissTimingConfigurationWithSecurityDTO dtoToEdit) {
manage2SailEventUrlJsonTextBox.setText(dtoToEdit.getJsonUrl());
hostnameTextBox.setText(dtoToEdit.getHostname());
portTextBox.setText(dtoToEdit.getPort() == null ? "" : ("" + dtoToEdit.getPort()));
updateUrlTextBox.setText(dtoToEdit.getUpdateURL());
updateUsernameTextBox.setText(dtoToEdit.getUpdateUsername());
updatePasswordTextBox.setText(dtoToEdit.getUpdatePassword());
validateAndUpdate();
}
private void createUi() {
grid = new Grid(8, 2);
grid.setWidget(0, 0, new Label(stringMessages.details() + ":"));
// Manage2SailEventId
final Label manage2SailEventIdLabel = new Label(stringMessages.manage2SailEventIdBox() + ":");
manage2SailEventIdLabel.setTitle(stringMessages.leaveEmptyForDefault());
manage2SailEventIdTextBox = createTextBox("");
manage2SailEventIdTextBox.ensureDebugId("Manage2SailEventIdTextBox");
manage2SailEventIdTextBox.setVisibleLength(40);
manage2SailEventIdTextBox.setTitle(stringMessages.manage2SailEventIdBox());
// update url with changed event id
manage2SailEventIdTextBox.addKeyUpHandler(e -> updateUrlFromEventId(manage2SailEventIdTextBox.getText()));
grid.setWidget(1, 0, manage2SailEventIdLabel);
grid.setWidget(1, 1, manage2SailEventIdTextBox);
// Manage2SailEventUrl
final Label manage2SailEventUrlJsonLabel = new Label(stringMessages.manage2SailEventURLBox() + ":");
manage2SailEventUrlJsonLabel.setTitle(stringMessages.leaveEmptyForDefault());
manage2SailEventUrlJsonTextBox = createTextBox("");
manage2SailEventUrlJsonTextBox.ensureDebugId("Manage2SailEventUrlJsonTextBox");
manage2SailEventUrlJsonTextBox.setVisibleLength(40);
manage2SailEventUrlJsonTextBox.setTitle(stringMessages.manage2SailEventURLBox());
grid.setWidget(2, 0, manage2SailEventUrlJsonLabel);
grid.setWidget(2, 1, manage2SailEventUrlJsonTextBox);
// Hostname
final Label hostnameLabel = new Label(stringMessages.hostname() + ":");
manage2SailEventUrlJsonLabel.setTitle(stringMessages.leaveEmptyForDefault());
hostnameTextBox = createTextBox("");
hostnameTextBox.ensureDebugId("HostnameTextBox");
hostnameTextBox.setVisibleLength(40);
hostnameTextBox.setTitle(stringMessages.hostname());
grid.setWidget(3, 0, hostnameLabel);
grid.setWidget(3, 1, hostnameTextBox);
// Port
final Label portLabel = new Label(stringMessages.manage2SailPort() + ":");
portLabel.setTitle(stringMessages.leaveEmptyForDefault());
portTextBox = createTextBox("");
portTextBox.ensureDebugId("PortTextBox");
portTextBox.setVisibleLength(40);
portTextBox.setTitle(stringMessages.manage2SailPort());
grid.setWidget(4, 0, portLabel);
grid.setWidget(4, 1, portTextBox);
// Update URL
final Label updateUrlLabel = new Label(stringMessages.swissTimingUpdateURL() + ":");
updateUrlLabel.setTitle(stringMessages.leaveEmptyForDefault());
updateUrlTextBox = createTextBox("");
updateUrlTextBox.ensureDebugId("UpdateUrlTextBox");
updateUrlTextBox.setVisibleLength(40);
updateUrlTextBox.setTitle(stringMessages.swissTimingUpdateURL());
grid.setWidget(5, 0, updateUrlLabel);
grid.setWidget(5, 1, updateUrlTextBox);
// Update Username
final Label updateUsernameLabel = new Label(stringMessages.swissTimingUpdateUsername() + ":");
updateUsernameLabel.setTitle(stringMessages.leaveEmptyForDefault());
updateUsernameTextBox = createTextBox("");
updateUsernameTextBox.ensureDebugId("UpdateUsernameTextBox");
updateUsernameTextBox.setVisibleLength(40);
updateUsernameTextBox.setTitle(stringMessages.swissTimingUpdateUsername());
grid.setWidget(6, 0, updateUsernameLabel);
grid.setWidget(6, 1, updateUsernameTextBox);
// Update Password
final Label updatePasswordLabel = new Label(stringMessages.swissTimingUpdatePassword() + ":");
updatePasswordLabel.setTitle(stringMessages.leaveEmptyForDefault());
updatePasswordTextBox = createPasswordTextBox("");
updatePasswordTextBox.ensureDebugId("UpdatePasswordTextBox");
updatePasswordTextBox.setVisibleLength(40);
updatePasswordTextBox.setTitle(stringMessages.swissTimingUpdatePassword());
grid.setWidget(7, 0, updatePasswordLabel);
grid.setWidget(7, 1, updatePasswordTextBox);
}
/**
* This function tries to infer a valid JsonUrl for any input given that matches the pattern of an event Id from
* M2S. If there is an event id detected the Json Url gets updated and the event Id textbox is filled with the
* detected event Id. The ID pattern is defined in {@link eventIdPattern}.
*/
private void updateUrlFromEventId(String eventIdText) {
final String result = SwissTimingEventIdUrlUtil.getUrlFromEventId(eventIdText);
if (result != null) {
manage2SailEventUrlJsonTextBox.setValue(result, /* fire events */ false);
}
}
@Override
protected FocusWidget getInitialFocusWidget() {
return manage2SailEventUrlJsonTextBox;
}
@Override
protected SwissTimingConfigurationWithSecurityDTO getResult() {
Integer port = null;
if (!portTextBox.getText().isEmpty()) {
try {
port = Integer.parseInt(portTextBox.getText());
} catch (NumberFormatException e) {
// port will be null.
}
}
return new SwissTimingConfigurationWithSecurityDTO(dtoToEdit.getName(),
manage2SailEventUrlJsonTextBox.getValue(), hostnameTextBox.getValue(), port,
updateUrlTextBox.getValue(), updateUsernameTextBox.getValue(), updatePasswordTextBox.getValue(),
dtoToEdit.getCreatorName());
}
@Override
protected Widget getAdditionalWidget() {
return grid;
}
}
@@ -0,0 +1,172 @@
package com.sap.sailing.gwt.ui.adminconsole.swisstiming;
import static com.sap.sse.security.ui.client.component.AccessControlledActionsColumn.create;
import java.util.ArrayList;
import java.util.List;
import com.google.gwt.user.cellview.client.AbstractCellTable;
import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Label;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.shared.SwissTimingArchiveConfigurationWithSecurityDTO;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.gwt.client.celltable.AbstractSortableTextColumn;
import com.sap.sse.gwt.client.celltable.CellTableWithCheckboxResources;
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel;
import com.sap.sse.gwt.client.celltable.TableWrapper;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel;
import com.sap.sse.security.shared.HasPermissions;
import com.sap.sse.security.shared.HasPermissions.DefaultActions;
import com.sap.sse.security.ui.client.UserService;
import com.sap.sse.security.ui.client.component.AccessControlledActionsColumn;
import com.sap.sse.security.ui.client.component.DefaultActionsImagesBarCell;
import com.sap.sse.security.ui.client.component.EditOwnershipDialog;
import com.sap.sse.security.ui.client.component.SecuredDTOOwnerColumn;
import com.sap.sse.security.ui.client.component.editacl.EditACLDialog;
import com.sap.sse.security.ui.client.i18n.StringMessages;
/**
* A wrapper for a CellTable displaying an overview over the existing Swiss Timing Archived Connections. It shows the
* User-Group name, the group and user each user group is owned by and options to delete the group, change the ownership
* or edit the associated ACL.
*/
public class SwissTimingArchivedConnectionTableWrapper extends
TableWrapper<SwissTimingArchiveConfigurationWithSecurityDTO, RefreshableSingleSelectionModel<SwissTimingArchiveConfigurationWithSecurityDTO>, StringMessages, CellTableWithCheckboxResources> {
private final LabeledAbstractFilterablePanel<SwissTimingArchiveConfigurationWithSecurityDTO> filterField;
private final SailingServiceAsync sailingServiceAsync;
private final com.sap.sailing.gwt.ui.client.StringMessages stringMessagesClient;
public SwissTimingArchivedConnectionTableWrapper(final UserService userService, final SailingServiceAsync sailingServiceAsync,
final com.sap.sailing.gwt.ui.client.StringMessages stringMessages, final ErrorReporter errorReporter,
final boolean enablePager, final CellTableWithCheckboxResources tableResources, final Runnable refresher) {
super(stringMessages, errorReporter, false, enablePager,
new EntityIdentityComparator<SwissTimingArchiveConfigurationWithSecurityDTO>() {
@Override
public boolean representSameEntity(SwissTimingArchiveConfigurationWithSecurityDTO dto1,
SwissTimingArchiveConfigurationWithSecurityDTO dto2) {
return dto1.getIdentifier().equals(dto2.getIdentifier());
}
@Override
public int hashCode(SwissTimingArchiveConfigurationWithSecurityDTO t) {
return t.getIdentifier().hashCode();
}
}, tableResources);
this.stringMessagesClient = stringMessages;
this.sailingServiceAsync = sailingServiceAsync;
final ListHandler<SwissTimingArchiveConfigurationWithSecurityDTO> swissTimingAccountColumnListHandler = getColumnSortHandler();
// table
final TextColumn<SwissTimingArchiveConfigurationWithSecurityDTO> swissTimingAccountJsonUrlColumn = new AbstractSortableTextColumn<SwissTimingArchiveConfigurationWithSecurityDTO>(
dto -> dto.getJsonUrl(), swissTimingAccountColumnListHandler);
final TextColumn<SwissTimingArchiveConfigurationWithSecurityDTO> swissTimingAccountCreatorNameColumn = new AbstractSortableTextColumn<SwissTimingArchiveConfigurationWithSecurityDTO>(
dto -> dto.getCreatorName(), swissTimingAccountColumnListHandler);
final HasPermissions type = SecuredDomainType.SWISS_TIMING_ARCHIVE_ACCOUNT;
final AccessControlledActionsColumn<SwissTimingArchiveConfigurationWithSecurityDTO, DefaultActionsImagesBarCell> actionColumn = create(
new DefaultActionsImagesBarCell(stringMessages), userService);
actionColumn.addAction(DefaultActionsImagesBarCell.ACTION_UPDATE, DefaultActions.UPDATE, dto -> {
new EditSwissTimingArchivedConnectionDialog(dto,
new DialogCallback<SwissTimingArchiveConfigurationWithSecurityDTO>() {
@Override
public void ok(final SwissTimingArchiveConfigurationWithSecurityDTO editedObject) {
sailingServiceAsync.updateSwissTimingArchiveConfiguration(editedObject,
new MarkedAsyncCallback<Void>(new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(
"Exception trying to update configuration in DB: " + caught.getMessage());
}
@Override
public void onSuccess(Void voidResult) {
refreshConnectionList();
}
}));
}
@Override
public void cancel() {
}
}, userService, errorReporter).show();
});
actionColumn.addAction(DefaultActionsImagesBarCell.ACTION_DELETE, DefaultActions.DELETE, dto -> {
sailingServiceAsync.deleteSwissTimingArchiveConfiguration(dto, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Exception trying to delete configuration in DB: " + caught.getMessage());
}
@Override
public void onSuccess(Void result) {
refreshConnectionList();
}
});
});
final EditOwnershipDialog.DialogConfig<SwissTimingArchiveConfigurationWithSecurityDTO> configOwnership = EditOwnershipDialog
.create(userService.getUserManagementService(), type, dto -> refreshConnectionList(),
stringMessages);
final EditACLDialog.DialogConfig<SwissTimingArchiveConfigurationWithSecurityDTO> configACL = EditACLDialog
.create(
userService.getUserManagementService(), type, dto -> dto.getAccessControlList(), stringMessages);
actionColumn.addAction(DefaultActionsImagesBarCell.ACTION_CHANGE_OWNERSHIP, DefaultActions.CHANGE_OWNERSHIP,
configOwnership::openDialog);
actionColumn.addAction(DefaultActionsImagesBarCell.ACTION_CHANGE_ACL, DefaultActions.CHANGE_ACL,
u -> configACL.openDialog(u));
filterField = new LabeledAbstractFilterablePanel<SwissTimingArchiveConfigurationWithSecurityDTO>(
new Label(stringMessages.filterSwissTimingAchivedConnections()),
new ArrayList<SwissTimingArchiveConfigurationWithSecurityDTO>(), dataProvider) {
@Override
public Iterable<String> getSearchableStrings(SwissTimingArchiveConfigurationWithSecurityDTO t) {
List<String> strings = new ArrayList<String>();
strings.add(t.getName());
strings.add(t.getCreatorName());
return strings;
}
@Override
public AbstractCellTable<SwissTimingArchiveConfigurationWithSecurityDTO> getCellTable() {
return table;
}
};
registerSelectionModelOnNewDataProvider(filterField.getAllListDataProvider());
mainPanel.insert(filterField, 0);
table.addColumnSortHandler(swissTimingAccountColumnListHandler);
table.addColumn(swissTimingAccountJsonUrlColumn, stringMessagesClient.jsonUrl());
table.addColumn(swissTimingAccountCreatorNameColumn, stringMessagesClient.creatorName());
SecuredDTOOwnerColumn.configureOwnerColumns(table, swissTimingAccountColumnListHandler, stringMessages);
table.addColumn(actionColumn, stringMessages.actions());
table.ensureDebugId("SwissTimingConfigurationWithSecurityDTOTable");
}
public LabeledAbstractFilterablePanel<SwissTimingArchiveConfigurationWithSecurityDTO> getFilterField() {
return filterField;
}
public void refreshConnectionList() {
sailingServiceAsync.getPreviousSwissTimingArchiveConfigurations(
new AsyncCallback<List<SwissTimingArchiveConfigurationWithSecurityDTO>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(
"Remote Procedure Call getPreviousSwissTimingArchiveConfigurations() - Failure: "
+ caught.getMessage());
}
@Override
public void onSuccess(List<SwissTimingArchiveConfigurationWithSecurityDTO> result) {
filterField.updateAll(result);
}
});
}
}
@@ -0,0 +1,204 @@
package com.sap.sailing.gwt.ui.adminconsole.swisstiming;
import static com.sap.sse.security.ui.client.component.AccessControlledActionsColumn.create;
import java.util.ArrayList;
import java.util.List;
import com.google.gwt.user.cellview.client.AbstractCellTable;
import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Label;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.shared.SwissTimingConfigurationWithSecurityDTO;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.gwt.client.celltable.AbstractSortableTextColumn;
import com.sap.sse.gwt.client.celltable.CellTableWithCheckboxResources;
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel;
import com.sap.sse.gwt.client.celltable.TableWrapper;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel;
import com.sap.sse.security.shared.HasPermissions;
import com.sap.sse.security.shared.HasPermissions.DefaultActions;
import com.sap.sse.security.ui.client.UserService;
import com.sap.sse.security.ui.client.component.AccessControlledActionsColumn;
import com.sap.sse.security.ui.client.component.DefaultActionsImagesBarCell;
import com.sap.sse.security.ui.client.component.EditOwnershipDialog;
import com.sap.sse.security.ui.client.component.SecuredDTOOwnerColumn;
import com.sap.sse.security.ui.client.component.editacl.EditACLDialog;
import com.sap.sse.security.ui.client.i18n.StringMessages;
/**
* A wrapper for a CellTable displaying an overview over the existing SwissTimingConnections. It shows the name, the
* event id, the event json url, the hostname, the port, the update url, the username for sending updates, the password
* for sending updates, the name of the creator, the group and user each user group is owned by. There are options edit
* or to delete the connection, change the ownership or edit the associated ACL. Editing the connection will open a new
* instance of {@link EditSwissTimingConnectionDialog}.
*/
public class SwissTimingConnectionTableWrapper extends
TableWrapper<SwissTimingConfigurationWithSecurityDTO, RefreshableSingleSelectionModel<SwissTimingConfigurationWithSecurityDTO>, StringMessages, CellTableWithCheckboxResources> {
private final LabeledAbstractFilterablePanel<SwissTimingConfigurationWithSecurityDTO> filterField;
private final SailingServiceAsync sailingServiceAsync;
private final com.sap.sailing.gwt.ui.client.StringMessages stringMessagesClient;
public SwissTimingConnectionTableWrapper(final UserService userService, final SailingServiceAsync sailingServiceAsync,
final com.sap.sailing.gwt.ui.client.StringMessages stringMessages, final ErrorReporter errorReporter,
final boolean enablePager, final CellTableWithCheckboxResources tableResources, final Runnable refresher) {
super(stringMessages, errorReporter, false, enablePager,
new EntityIdentityComparator<SwissTimingConfigurationWithSecurityDTO>() {
@Override
public boolean representSameEntity(SwissTimingConfigurationWithSecurityDTO dto1,
SwissTimingConfigurationWithSecurityDTO dto2) {
return dto1.getIdentifier().equals(dto2.getIdentifier());
}
@Override
public int hashCode(SwissTimingConfigurationWithSecurityDTO t) {
return t.getIdentifier().hashCode();
}
}, tableResources);
this.stringMessagesClient = stringMessages;
this.sailingServiceAsync = sailingServiceAsync;
final ListHandler<SwissTimingConfigurationWithSecurityDTO> swissTimingConectionColumnListHandler = getColumnSortHandler();
// table
final TextColumn<SwissTimingConfigurationWithSecurityDTO> swissTimingConnectionNameColumn = new AbstractSortableTextColumn<SwissTimingConfigurationWithSecurityDTO>(
dto -> dto.getName(), swissTimingConectionColumnListHandler);
final TextColumn<SwissTimingConfigurationWithSecurityDTO> swissTimingConnectionManage2SailEventIdColumn = new AbstractSortableTextColumn<SwissTimingConfigurationWithSecurityDTO>(
dto -> SwissTimingEventIdUrlUtil.getEventIdFromUrl(dto.getJsonUrl()),
swissTimingConectionColumnListHandler);
final TextColumn<SwissTimingConfigurationWithSecurityDTO> swissTimingConnectionJsonUrlColumn = new AbstractSortableTextColumn<SwissTimingConfigurationWithSecurityDTO>(
dto -> dto.getJsonUrl(), swissTimingConectionColumnListHandler);
final TextColumn<SwissTimingConfigurationWithSecurityDTO> swissTimingConnectionHostnameColumn = new AbstractSortableTextColumn<SwissTimingConfigurationWithSecurityDTO>(
dto -> dto.getHostname(), swissTimingConectionColumnListHandler);
final TextColumn<SwissTimingConfigurationWithSecurityDTO> swissTimingConnectionPortColumn = new AbstractSortableTextColumn<SwissTimingConfigurationWithSecurityDTO>(
dto -> dto.getPort() == null ? "" : ("" + dto.getPort()), swissTimingConectionColumnListHandler);
final TextColumn<SwissTimingConfigurationWithSecurityDTO> swissTimingConnectionUpdateUrlColumn = new AbstractSortableTextColumn<SwissTimingConfigurationWithSecurityDTO>(
dto -> dto.getUpdateURL(), swissTimingConectionColumnListHandler);
final TextColumn<SwissTimingConfigurationWithSecurityDTO> swissTimingConnectionUpdateUsernameColumn = new AbstractSortableTextColumn<SwissTimingConfigurationWithSecurityDTO>(
dto -> dto.getUpdateUsername(), swissTimingConectionColumnListHandler);
final TextColumn<SwissTimingConfigurationWithSecurityDTO> swissTimingConnectionCreatorNameColumn = new AbstractSortableTextColumn<SwissTimingConfigurationWithSecurityDTO>(
dto -> dto.getCreatorName(), swissTimingConectionColumnListHandler);
final HasPermissions type = SecuredDomainType.SWISS_TIMING_ACCOUNT;
final AccessControlledActionsColumn<SwissTimingConfigurationWithSecurityDTO, DefaultActionsImagesBarCell> actionColumn = create(
new DefaultActionsImagesBarCell(stringMessages), userService);
actionColumn.addAction(DefaultActionsImagesBarCell.ACTION_UPDATE, DefaultActions.UPDATE, dto -> {
new EditSwissTimingConnectionDialog(dto, new DialogCallback<SwissTimingConfigurationWithSecurityDTO>() {
@Override
public void ok(final SwissTimingConfigurationWithSecurityDTO editedObject) {
sailingServiceAsync.updateSwissTimingConfiguration(editedObject,
new MarkedAsyncCallback<Void>(new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(
"Exception trying to update configuration in DB: " + caught.getMessage());
}
@Override
public void onSuccess(Void voidResult) {
refreshSwissTimingConnectionList();
}
}));
}
@Override
public void cancel() {
}
}, userService, errorReporter).show();
});
actionColumn.addAction(DefaultActionsImagesBarCell.ACTION_DELETE, DefaultActions.DELETE, dto -> {
sailingServiceAsync.deleteSwissTimingConfiguration(dto, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Exception trying to delete configuration in DB: " + caught.getMessage());
}
@Override
public void onSuccess(Void result) {
refreshSwissTimingConnectionList();
}
});
});
final EditOwnershipDialog.DialogConfig<SwissTimingConfigurationWithSecurityDTO> configOwnership = EditOwnershipDialog
.create(userService.getUserManagementService(), type, dto -> refreshSwissTimingConnectionList(),
stringMessages);
final EditACLDialog.DialogConfig<SwissTimingConfigurationWithSecurityDTO> configACL = EditACLDialog.create(
userService.getUserManagementService(), type, dto -> dto.getAccessControlList(), stringMessages);
actionColumn.addAction(DefaultActionsImagesBarCell.ACTION_CHANGE_OWNERSHIP, DefaultActions.CHANGE_OWNERSHIP,
configOwnership::openDialog);
actionColumn.addAction(DefaultActionsImagesBarCell.ACTION_CHANGE_ACL, DefaultActions.CHANGE_ACL,
u -> configACL.openDialog(u));
filterField = new LabeledAbstractFilterablePanel<SwissTimingConfigurationWithSecurityDTO>(
new Label(stringMessagesClient.filterSwissTimingConnections()),
new ArrayList<SwissTimingConfigurationWithSecurityDTO>(), dataProvider) {
@Override
public Iterable<String> getSearchableStrings(SwissTimingConfigurationWithSecurityDTO t) {
List<String> strings = new ArrayList<String>();
strings.add(t.getName());
if (t.getUpdateUsername() != null) {
strings.add(t.getUpdateUsername());
}
strings.add(t.getCreatorName());
if (t.getHostname() != null) {
strings.add(t.getHostname());
}
if (t.getPort() != null) {
strings.add(t.getPort().toString());
}
return strings;
}
@Override
public AbstractCellTable<SwissTimingConfigurationWithSecurityDTO> getCellTable() {
return table;
}
};
registerSelectionModelOnNewDataProvider(filterField.getAllListDataProvider());
mainPanel.insert(filterField, 0);
table.addColumnSortHandler(swissTimingConectionColumnListHandler);
table.addColumn(swissTimingConnectionNameColumn, getStringMessages().name());
table.addColumn(swissTimingConnectionManage2SailEventIdColumn, stringMessagesClient.manage2SailEventIdBox());
table.addColumn(swissTimingConnectionJsonUrlColumn, stringMessagesClient.manage2SailEventURLBox());
table.addColumn(swissTimingConnectionHostnameColumn, stringMessagesClient.hostname());
table.addColumn(swissTimingConnectionPortColumn, stringMessagesClient.manage2SailPort());
table.addColumn(swissTimingConnectionUpdateUrlColumn, stringMessagesClient.updateURL());
table.addColumn(swissTimingConnectionUpdateUsernameColumn, stringMessagesClient.username());
table.addColumn(swissTimingConnectionCreatorNameColumn, stringMessagesClient.creatorName());
SecuredDTOOwnerColumn.configureOwnerColumns(table, swissTimingConectionColumnListHandler, stringMessages);
table.addColumn(actionColumn, stringMessages.actions());
table.ensureDebugId("SwissTimingConfigurationWithSecurityDTOTable");
}
public LabeledAbstractFilterablePanel<SwissTimingConfigurationWithSecurityDTO> getFilterField() {
return filterField;
}
public void refreshSwissTimingConnectionList() {
sailingServiceAsync
.getPreviousSwissTimingConfigurations(
new AsyncCallback<List<SwissTimingConfigurationWithSecurityDTO>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter
.reportError(
"Remote Procedure Call getPreviousSwissTimingConfigurations() - Failure: "
+ caught.getMessage());
}
@Override
public void onSuccess(List<SwissTimingConfigurationWithSecurityDTO> result) {
filterField.updateAll(result);
}
});
}
}
@@ -0,0 +1,35 @@
package com.sap.sailing.gwt.ui.adminconsole.swisstiming;
public final class SwissTimingEventIdUrlUtil {
private static final String EVENT_ID_PATTERN = "[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}";
private static final String MANAGE2SAIL_API_BASE_URL = "http://manage2sail.com/api/public/links/event/";
private static final String MANAGE2SAIL_API_ACCESS_TOKEN = "?accesstoken=bDAv8CwsTM94ujZ";
private static final String MANAGE2SAIL_API_APPENDIX = "&mediaType=json&includeRaces=true";
/**
* Similar to {@link #updateUrlFromEventId} this function tries to extract a M2S event Id by looking at the given
* url.
*
* @return The event ID inferred from the Json Url.
*/
public static String getEventIdFromUrl(String eventUrl) {
if (eventUrl.matches("http://manage2sail.com/.*" + EVENT_ID_PATTERN + ".*")) {
return eventUrl.replaceFirst(".*(" + EVENT_ID_PATTERN + ").*", "$1");
}
return null;
}
/**
* This function tries to infer a valid JsonUrl for any input given that matches the pattern of an event Id from
* M2S. If there is an event id detected the Json Url gets returned.The ID pattern is defined in
* {@link eventIdPattern}.
*/
public static String getUrlFromEventId(String eventIdText) {
if (eventIdText.matches(".*" + EVENT_ID_PATTERN + ".*")) {
final String inferredEventId = eventIdText.replaceFirst(".*(" + EVENT_ID_PATTERN + ").*", "$1");
return MANAGE2SAIL_API_BASE_URL + inferredEventId + MANAGE2SAIL_API_ACCESS_TOKEN + MANAGE2SAIL_API_APPENDIX;
}
return null;
}
}
@@ -0,0 +1,131 @@
package com.sap.sailing.gwt.ui.adminconsole.tractrac;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.TracTracConfigurationWithSecurityDTO;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
import com.sap.sse.security.ui.client.UserService;
/**
* Edits a {@link TracTracConfigurationWithSecurityDTO} object. Can be accessed from
* {@link TracTracConnectionTableWrapper}
*/
public class EditTracTracConnectionDialog extends DataEntryDialog<TracTracConfigurationWithSecurityDTO> {
private static final StringMessages stringMessages = StringMessages.INSTANCE;
private Grid grid;
private TextBox storedURITextBox;
private TextBox liveURITextBox;
private TextBox jsonURLTextBox;
private TextBox tracTracUpdateURITextBox;
private TextBox tractracUsernameTextBox;
private PasswordTextBox tractracPasswordTextBox;
private final TracTracConfigurationWithSecurityDTO dtoToEdit;
/**
* The class creates the UI-dialog to type in the Data about a the selected trac trac account.
*
* @param userToEdit
* The 'userToEdit' parameter contains the user which should be changed or initialized.
*/
public EditTracTracConnectionDialog(final TracTracConfigurationWithSecurityDTO dtotoEdit,
final DialogCallback<TracTracConfigurationWithSecurityDTO> callback, final UserService userService,
final ErrorReporter errorReporter) {
super(stringMessages.editTracTracConnection(), null, stringMessages.ok(), stringMessages.cancel(),
/* validator */ null, /* animationEnabled */true, callback);
this.dtoToEdit = dtotoEdit;
this.ensureDebugId("EditTracTracConnectionDialog");
createUi();
setData(dtotoEdit);
}
private void setData(final TracTracConfigurationWithSecurityDTO dtotoEdit) {
storedURITextBox.setText(dtotoEdit.getStoredDataURI());
liveURITextBox.setText(dtotoEdit.getLiveDataURI());
jsonURLTextBox.setText(dtotoEdit.getJsonUrl());
tracTracUpdateURITextBox.setText(dtotoEdit.getCourseDesignUpdateURI());
tractracUsernameTextBox.setText(dtotoEdit.getTracTracUsername());
tractracPasswordTextBox.setText(dtotoEdit.getTracTracPassword());
super.getOkButton().setEnabled(!jsonURLTextBox.getText().isEmpty());
}
private void createUi() {
grid = new Grid(7, 2);
grid.setWidget(0, 0, new Label(stringMessages.details() + ":"));
Label liveURILabel = new Label(stringMessages.liveUri() + ":");
liveURILabel.setTitle(stringMessages.leaveEmptyForDefault());
liveURITextBox = createTextBox("");
liveURITextBox.ensureDebugId("LiveURITextBox");
liveURITextBox.setVisibleLength(40);
liveURITextBox.setTitle(stringMessages.leaveEmptyForDefault());
grid.setWidget(1, 0, liveURILabel);
grid.setWidget(1, 1, liveURITextBox);
Label storedURILabel = new Label(stringMessages.storedUri() + ":");
storedURILabel.setTitle(stringMessages.leaveEmptyForDefault());
storedURITextBox = createTextBox("");
storedURITextBox.ensureDebugId("StoredURITextBox");
storedURITextBox.setVisibleLength(40);
storedURITextBox.setTitle(stringMessages.leaveEmptyForDefault());
grid.setWidget(2, 0, storedURILabel);
grid.setWidget(2, 1, storedURITextBox);
// JSON URL
Label jsonURLLabel = new Label(stringMessages.jsonUrl() + ":");
jsonURLTextBox = createTextBox("");
jsonURLTextBox.ensureDebugId("JsonURLTextBox");
jsonURLTextBox.setVisibleLength(100);
// validation: User should not create empty connections
jsonURLTextBox.addKeyUpHandler(e -> super.getOkButton().setEnabled(!jsonURLTextBox.getText().isEmpty()));
grid.setWidget(3, 0, jsonURLLabel);
grid.setWidget(3, 1, jsonURLTextBox);
// Course design Update URL
Label tracTracUpdateURLLabel = new Label(stringMessages.tracTracUpdateUrl() + ":");
tracTracUpdateURITextBox = createTextBox("");
tracTracUpdateURITextBox.ensureDebugId("TracTracUpdateURITextBox");
tracTracUpdateURITextBox.setVisibleLength(100);
grid.setWidget(4, 0, tracTracUpdateURLLabel);
grid.setWidget(4, 1, tracTracUpdateURITextBox);
// TracTrac Username
tractracUsernameTextBox = createTextBox("");
tractracUsernameTextBox.ensureDebugId("TracTracUsernameTextBox");
tractracUsernameTextBox.setVisibleLength(40);
grid.setWidget(5, 0, new Label(stringMessages.tractracUsername() + ":"));
grid.setWidget(5, 1, tractracUsernameTextBox);
// TracTrac Password
tractracPasswordTextBox = createPasswordTextBox("");
tractracPasswordTextBox.ensureDebugId("TracTracPasswordTextBox");
tractracPasswordTextBox.setVisibleLength(40);
grid.setWidget(6, 0, new Label(stringMessages.tractracPassword() + ":"));
grid.setWidget(6, 1, tractracPasswordTextBox);
}
@Override
protected FocusWidget getInitialFocusWidget() {
return jsonURLTextBox;
}
@Override
protected TracTracConfigurationWithSecurityDTO getResult() {
final String jsonURL = jsonURLTextBox.getValue();
final String liveDataURI = liveURITextBox.getValue();
final String storedDataURI = storedURITextBox.getValue();
final String courseDesignUpdateURI = tracTracUpdateURITextBox.getValue();
final String tractracUsername = tractracUsernameTextBox.getValue();
final String tractracPassword = tractracPasswordTextBox.getValue();
return new TracTracConfigurationWithSecurityDTO(dtoToEdit.getName(), jsonURL, liveDataURI, storedDataURI,
courseDesignUpdateURI, tractracUsername, tractracPassword, dtoToEdit.getCreatorName());
}
@Override
protected Widget getAdditionalWidget() {
return grid;
}
}
@@ -0,0 +1,185 @@
package com.sap.sailing.gwt.ui.adminconsole.tractrac;
import static com.sap.sse.security.ui.client.component.AccessControlledActionsColumn.create;
import java.util.ArrayList;
import java.util.List;
import com.google.gwt.user.cellview.client.AbstractCellTable;
import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Label;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.shared.TracTracConfigurationWithSecurityDTO;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.gwt.client.celltable.AbstractSortableTextColumn;
import com.sap.sse.gwt.client.celltable.CellTableWithCheckboxResources;
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel;
import com.sap.sse.gwt.client.celltable.TableWrapper;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel;
import com.sap.sse.security.shared.HasPermissions;
import com.sap.sse.security.shared.HasPermissions.DefaultActions;
import com.sap.sse.security.ui.client.UserService;
import com.sap.sse.security.ui.client.component.AccessControlledActionsColumn;
import com.sap.sse.security.ui.client.component.DefaultActionsImagesBarCell;
import com.sap.sse.security.ui.client.component.EditOwnershipDialog;
import com.sap.sse.security.ui.client.component.SecuredDTOOwnerColumn;
import com.sap.sse.security.ui.client.component.editacl.EditACLDialog;
import com.sap.sse.security.ui.client.i18n.StringMessages;
/**
* A wrapper for a CellTable displaying an overview over the existing TracTracConnections. It shows the name, the Live
* URI, the Stored Uri, the JSON URL, the TracTrac Server Update URI, the TracTrac user name, the TracTrac password, the
* name of the creator, the group and user each user group is owned by. There are options edit or to delete the
* connection, change the ownership or edit the associated ACL. Editing the connection will open a new instance of
* {@link EditTracTracConnectionDialog}.
*/
public class TracTracConnectionTableWrapper extends
TableWrapper<TracTracConfigurationWithSecurityDTO, RefreshableSingleSelectionModel<TracTracConfigurationWithSecurityDTO>, StringMessages, CellTableWithCheckboxResources> {
private final LabeledAbstractFilterablePanel<TracTracConfigurationWithSecurityDTO> filterField;
private final SailingServiceAsync sailingServiceAsync;
private final com.sap.sailing.gwt.ui.client.StringMessages stringMessagesClient;
public TracTracConnectionTableWrapper(final UserService userService, final SailingServiceAsync sailingServiceAsync,
final com.sap.sailing.gwt.ui.client.StringMessages stringMessages, final ErrorReporter errorReporter,
final boolean enablePager, final CellTableWithCheckboxResources tableResources, final Runnable refresher) {
super(stringMessages, errorReporter, false, enablePager,
new EntityIdentityComparator<TracTracConfigurationWithSecurityDTO>() {
@Override
public boolean representSameEntity(TracTracConfigurationWithSecurityDTO dto1,
TracTracConfigurationWithSecurityDTO dto2) {
return dto1.getIdentifier().equals(dto2.getIdentifier());
}
@Override
public int hashCode(TracTracConfigurationWithSecurityDTO t) {
return t.getIdentifier().hashCode();
}
}, tableResources);
this.stringMessagesClient = stringMessages;
this.sailingServiceAsync = sailingServiceAsync;
final ListHandler<TracTracConfigurationWithSecurityDTO> tracTracAccountColumnListHandler = getColumnSortHandler();
// table
final TextColumn<TracTracConfigurationWithSecurityDTO> tracTracAccountNameColumn = new AbstractSortableTextColumn<TracTracConfigurationWithSecurityDTO>(
dto -> dto.getName(), tracTracAccountColumnListHandler);
final TextColumn<TracTracConfigurationWithSecurityDTO> tracTracAccountLiveUriColumn = new AbstractSortableTextColumn<TracTracConfigurationWithSecurityDTO>(
dto -> dto.getLiveDataURI(), tracTracAccountColumnListHandler);
final TextColumn<TracTracConfigurationWithSecurityDTO> tracTracAccountStoredUriColumn = new AbstractSortableTextColumn<TracTracConfigurationWithSecurityDTO>(
dto -> dto.getStoredDataURI(), tracTracAccountColumnListHandler);
final TextColumn<TracTracConfigurationWithSecurityDTO> tracTracAccountJsonUrlColumn = new AbstractSortableTextColumn<TracTracConfigurationWithSecurityDTO>(
dto -> dto.getJsonUrl(), tracTracAccountColumnListHandler);
final TextColumn<TracTracConfigurationWithSecurityDTO> tracTracAccountTracTracServerUpdateUriColumn = new AbstractSortableTextColumn<TracTracConfigurationWithSecurityDTO>(
dto -> dto.getCourseDesignUpdateURI(), tracTracAccountColumnListHandler);
final TextColumn<TracTracConfigurationWithSecurityDTO> tracTracAccountUsernameColumn = new AbstractSortableTextColumn<TracTracConfigurationWithSecurityDTO>(
dto -> dto.getTracTracUsername(), tracTracAccountColumnListHandler);
final TextColumn<TracTracConfigurationWithSecurityDTO> tracTracAccountCreatorNameColumn = new AbstractSortableTextColumn<TracTracConfigurationWithSecurityDTO>(
dto -> dto.getCreatorName(), tracTracAccountColumnListHandler);
final HasPermissions type = SecuredDomainType.TRACTRAC_ACCOUNT;
final AccessControlledActionsColumn<TracTracConfigurationWithSecurityDTO, DefaultActionsImagesBarCell> actionColumn = create(
new DefaultActionsImagesBarCell(stringMessages), userService);
actionColumn.addAction(DefaultActionsImagesBarCell.ACTION_UPDATE, DefaultActions.UPDATE, dto -> {
new EditTracTracConnectionDialog(dto, new DialogCallback<TracTracConfigurationWithSecurityDTO>() {
@Override
public void ok(final TracTracConfigurationWithSecurityDTO editedObject) {
sailingServiceAsync.updateTracTracConfiguration(editedObject,
new MarkedAsyncCallback<Void>(new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(
"Exception trying to update configuration in DB: " + caught.getMessage());
}
@Override
public void onSuccess(Void voidResult) {
refreshTracTracConnectionList();
}
}));
}
@Override
public void cancel() {
}
}, userService, errorReporter).show();
});
actionColumn.addAction(DefaultActionsImagesBarCell.ACTION_DELETE, DefaultActions.DELETE, dto -> {
sailingServiceAsync.deleteTracTracConfiguration(dto, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Exception trying to delete configuration in DB: " + caught.getMessage());
}
@Override
public void onSuccess(Void result) {
refreshTracTracConnectionList();
}
});
});
final EditOwnershipDialog.DialogConfig<TracTracConfigurationWithSecurityDTO> configOwnership = EditOwnershipDialog
.create(userService.getUserManagementService(), type, dto -> refreshTracTracConnectionList(),
stringMessages);
final EditACLDialog.DialogConfig<TracTracConfigurationWithSecurityDTO> configACL = EditACLDialog.create(
userService.getUserManagementService(), type, dto -> dto.getAccessControlList(), stringMessages);
actionColumn.addAction(DefaultActionsImagesBarCell.ACTION_CHANGE_OWNERSHIP, DefaultActions.CHANGE_OWNERSHIP,
configOwnership::openDialog);
actionColumn.addAction(DefaultActionsImagesBarCell.ACTION_CHANGE_ACL, DefaultActions.CHANGE_ACL,
u -> configACL.openDialog(u));
filterField = new LabeledAbstractFilterablePanel<TracTracConfigurationWithSecurityDTO>(
new Label(stringMessages.filterTracTracConnections()),
new ArrayList<TracTracConfigurationWithSecurityDTO>(), dataProvider) {
@Override
public Iterable<String> getSearchableStrings(TracTracConfigurationWithSecurityDTO t) {
List<String> strings = new ArrayList<String>();
strings.add(t.getName());
strings.add(t.getCreatorName());
return strings;
}
@Override
public AbstractCellTable<TracTracConfigurationWithSecurityDTO> getCellTable() {
return table;
}
};
registerSelectionModelOnNewDataProvider(filterField.getAllListDataProvider());
mainPanel.insert(filterField, 0);
table.addColumnSortHandler(tracTracAccountColumnListHandler);
table.addColumn(tracTracAccountNameColumn, getStringMessages().name());
table.addColumn(tracTracAccountLiveUriColumn, stringMessagesClient.liveUri());
table.addColumn(tracTracAccountStoredUriColumn, stringMessagesClient.storedUri());
table.addColumn(tracTracAccountJsonUrlColumn, stringMessagesClient.jsonUrl());
table.addColumn(tracTracAccountTracTracServerUpdateUriColumn, stringMessagesClient.tracTracUpdateUrl());
table.addColumn(tracTracAccountUsernameColumn, stringMessagesClient.tractracUsername());
table.addColumn(tracTracAccountCreatorNameColumn, stringMessagesClient.creatorName());
SecuredDTOOwnerColumn.configureOwnerColumns(table, tracTracAccountColumnListHandler, stringMessages);
table.addColumn(actionColumn, stringMessages.actions());
table.ensureDebugId("TracTracConfigurationWithSecurityDTOTable");
}
public LabeledAbstractFilterablePanel<TracTracConfigurationWithSecurityDTO> getFilterField() {
return filterField;
}
public void refreshTracTracConnectionList() {
sailingServiceAsync
.getPreviousTracTracConfigurations(new AsyncCallback<List<TracTracConfigurationWithSecurityDTO>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter
.reportError("Remote Procedure Call getPreviousTracTracConfigurations() - Failure: "
+ caught.getMessage());
}
@Override
public void onSuccess(List<TracTracConfigurationWithSecurityDTO> result) {
filterField.updateAll(result);
}
});
}
}
@@ -82,7 +82,6 @@ import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO.RegattaConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.DeviceConfigurationWithSecurityDTO;
import com.sap.sailing.gwt.ui.shared.DeviceMappingDTO;
import com.sap.sailing.gwt.ui.shared.EventBaseDTO;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTOWithSpeedWindTackAndLegType;
@@ -110,12 +109,12 @@ import com.sap.sailing.gwt.ui.shared.SimulatorResultsDTO;
import com.sap.sailing.gwt.ui.shared.SliceRacePreperationDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTOWithSecurity;
import com.sap.sailing.gwt.ui.shared.SwissTimingArchiveConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingArchiveConfigurationWithSecurityDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingConfigurationWithSecurityDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingEventRecordDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingRaceRecordDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingReplayRaceDTO;
import com.sap.sailing.gwt.ui.shared.TracTracConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.TracTracConfigurationWithSecurityDTO;
import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO;
import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO;
import com.sap.sailing.gwt.ui.shared.TypedDeviceMappingDTO;
@@ -148,7 +147,14 @@ import com.sap.sse.security.ui.shared.SuccessInfo;
* service methods, an empty (non-<code>null</code>) result is returned.
*/
public interface SailingService extends RemoteService, FileStorageManagementGwtService, RemoteReplicationService {
List<TracTracConfigurationDTO> getPreviousTracTracConfigurations() throws UnauthorizedException, Exception;
List<TracTracConfigurationWithSecurityDTO> getPreviousTracTracConfigurations()
throws UnauthorizedException, Exception;
void updateTracTracConfiguration(TracTracConfigurationWithSecurityDTO tracTracConfiguration)
throws UnauthorizedException, Exception;
void deleteTracTracConfiguration(TracTracConfigurationWithSecurityDTO tracTracConfiguration)
throws UnauthorizedException, Exception;
List<RegattaDTO> getRegattas() throws UnauthorizedException;
@@ -156,9 +162,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
RegattaDTO getRegattaByName(String regattaName) throws UnauthorizedException;
List<EventDTO> getEvents() throws UnauthorizedException, Exception;
List<EventBaseDTO> getPublicEventsOfAllSailingServers() throws UnauthorizedException, Exception;
List<EventDTO> getEvents() throws Exception;
Util.Pair<String, List<TracTracRaceRecordDTO>> listTracTracRacesInEvent(String eventJsonURL,
boolean listHiddenRaces) throws UnauthorizedException, Exception;
@@ -176,7 +180,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm)
throws UnauthorizedException;
void storeTracTracConfiguration(String name, String jsonURL, String liveDataURI, String storedDataURI,
void createTracTracConfiguration(String name, String jsonURL, String liveDataURI, String storedDataURI,
String courseDesignUpdateURI, String tracTracUsername, String tracTracPassword)
throws UnauthorizedException, Exception;
@@ -364,13 +368,19 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
void updateIsMedalRace(String leaderboardName, String columnName, boolean isMedalRace) throws UnauthorizedException;
List<SwissTimingConfigurationDTO> getPreviousSwissTimingConfigurations() throws UnauthorizedException;
List<SwissTimingConfigurationWithSecurityDTO> getPreviousSwissTimingConfigurations() throws UnauthorizedException;
SwissTimingEventRecordDTO getRacesOfSwissTimingEvent(String eventJsonURL) throws UnauthorizedException, Exception;
void storeSwissTimingConfiguration(String configName, String jsonURL, String hostname, Integer port,
void createSwissTimingConfiguration(String configName, String jsonURL, String hostname, Integer port,
String updateURL, String updateUsername, String updatePassword) throws UnauthorizedException, Exception;
void deleteSwissTimingConfiguration(SwissTimingConfigurationWithSecurityDTO configuration)
throws UnauthorizedException, Exception;
void updateSwissTimingConfiguration(SwissTimingConfigurationWithSecurityDTO configuration)
throws UnauthorizedException, Exception;
String[] getCountryCodes() throws UnauthorizedException;
Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>> getDouglasPoints(
@@ -505,9 +515,16 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
List<String> getOverallLeaderboardNamesContaining(String leaderboardName) throws UnauthorizedException;
List<SwissTimingArchiveConfigurationDTO> getPreviousSwissTimingArchiveConfigurations() throws UnauthorizedException;
List<SwissTimingArchiveConfigurationWithSecurityDTO> getPreviousSwissTimingArchiveConfigurations() throws UnauthorizedException;
void storeSwissTimingArchiveConfiguration(String swissTimingUrl) throws UnauthorizedException, Exception;
void createSwissTimingArchiveConfiguration(String jsonUrl)
throws UnauthorizedException, Exception;
void updateSwissTimingArchiveConfiguration(SwissTimingArchiveConfigurationWithSecurityDTO dto)
throws UnauthorizedException, Exception;
void deleteSwissTimingArchiveConfiguration(SwissTimingArchiveConfigurationWithSecurityDTO dto)
throws UnauthorizedException, Exception;
void createCourseAreas(UUID eventId, String[] courseAreaNames) throws UnauthorizedException;
@@ -1125,8 +1142,6 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
*/
String openRegattaRegistrationQrCode(String url) throws UnauthorizedException;
void setDefaultTenantForCurrentServer(String tennant) throws UnauthorizedException;
List<String> getPossibleTennants() throws UnauthorizedException;
void updateGroupOwnerForEventHierarchy(UUID eventId,
@@ -1159,4 +1174,10 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
*/
CompetitorDTO getCompetitor(UUID competitorId, String leaderboardName,
String regattaRegistrationLinkSecret);
boolean existsSwissTimingConfigurationForCurrentUser(String jsonUrl) throws Exception, UnauthorizedException;
boolean existsSwissTimingArchiveConfigurationForCurrentUser(String jsonUrl) throws Exception, UnauthorizedException;
boolean existsTracTracConfigurationForCurrentUser(String jsonUrl) throws Exception, UnauthorizedException;
}
@@ -9,6 +9,8 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.apache.shiro.authz.AuthorizationException;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
@@ -66,7 +68,6 @@ import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO.RegattaConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.DeviceConfigurationWithSecurityDTO;
import com.sap.sailing.gwt.ui.shared.DeviceMappingDTO;
import com.sap.sailing.gwt.ui.shared.EventBaseDTO;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTOWithSpeedWindTackAndLegType;
@@ -95,12 +96,12 @@ import com.sap.sailing.gwt.ui.shared.SimulatorResultsDTO;
import com.sap.sailing.gwt.ui.shared.SliceRacePreperationDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTOWithSecurity;
import com.sap.sailing.gwt.ui.shared.SwissTimingArchiveConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingArchiveConfigurationWithSecurityDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingConfigurationWithSecurityDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingEventRecordDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingRaceRecordDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingReplayRaceDTO;
import com.sap.sailing.gwt.ui.shared.TracTracConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.TracTracConfigurationWithSecurityDTO;
import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO;
import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO;
import com.sap.sailing.gwt.ui.shared.TypedDeviceMappingDTO;
@@ -181,11 +182,17 @@ public interface SailingServiceAsync extends FileStorageManagementGwtServiceAsyn
boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm,
AsyncCallback<Void> asyncCallback);
void getPreviousTracTracConfigurations(AsyncCallback<List<TracTracConfigurationDTO>> callback);
void getPreviousTracTracConfigurations(AsyncCallback<List<TracTracConfigurationWithSecurityDTO>> callback);
void storeTracTracConfiguration(String name, String jsonURL, String liveDataURI, String storedDataURI,
void createTracTracConfiguration(String name, String jsonURL, String liveDataURI, String storedDataURI,
String courseDesignUpdateURI, String tracTracUsername, String tracTracPassword, AsyncCallback<Void> callback);
void deleteTracTracConfiguration(TracTracConfigurationWithSecurityDTO tracTracConfiguration,
AsyncCallback<Void> callback);
void updateTracTracConfiguration(TracTracConfigurationWithSecurityDTO tracTracConfiguration,
AsyncCallback<Void> callback);
void stopTrackingRaces(Iterable<RegattaAndRaceIdentifier> racesToStopTracking, AsyncCallback<Void> asyncCallback);
/**
@@ -357,13 +364,19 @@ public interface SailingServiceAsync extends FileStorageManagementGwtServiceAsyn
void updateRacesDelayToLive(List<RegattaAndRaceIdentifier> regattaAndRaceIdentifiers, long delayToLiveInMs,
AsyncCallback<Void> callback);
void getPreviousSwissTimingConfigurations(AsyncCallback<List<SwissTimingConfigurationDTO>> asyncCallback);
void getPreviousSwissTimingConfigurations(AsyncCallback<List<SwissTimingConfigurationWithSecurityDTO>> asyncCallback);
void getRacesOfSwissTimingEvent(String eventJsonUrl, AsyncCallback<SwissTimingEventRecordDTO> asyncCallback);
void storeSwissTimingConfiguration(String configName, String jsonURL, String hostname, Integer port,
void createSwissTimingConfiguration(String configName, String jsonURL, String hostname, Integer port,
String updateURL, String updateUsername, String updatePassword, AsyncCallback<Void> asyncCallback);
void updateSwissTimingConfiguration(SwissTimingConfigurationWithSecurityDTO configuration,
AsyncCallback<Void> asyncCallback);
void deleteSwissTimingConfiguration(SwissTimingConfigurationWithSecurityDTO configuration,
AsyncCallback<Void> asyncCallback);
void getCountryCodes(AsyncCallback<String[]> callback);
void getDouglasPoints(RegattaAndRaceIdentifier raceIdentifier, Map<CompetitorDTO, TimeRange> competitorTimeRanges,
@@ -436,8 +449,6 @@ public interface SailingServiceAsync extends FileStorageManagementGwtServiceAsyn
void getEvents(AsyncCallback<List<EventDTO>> callback);
void getPublicEventsOfAllSailingServers(AsyncCallback<List<EventBaseDTO>> callback);
/**
* Renames the event with the name <code>oldName</code> to the <code>newName</code>.<br />
* If there's no event with the name <code>oldName</code> or there's already a event with the name
@@ -572,9 +583,16 @@ public interface SailingServiceAsync extends FileStorageManagementGwtServiceAsyn
void getOverallLeaderboardNamesContaining(String leaderboardName, AsyncCallback<List<String>> asyncCallback);
void getPreviousSwissTimingArchiveConfigurations(
AsyncCallback<List<SwissTimingArchiveConfigurationDTO>> asyncCallback);
AsyncCallback<List<SwissTimingArchiveConfigurationWithSecurityDTO>> asyncCallback);
void storeSwissTimingArchiveConfiguration(String swissTimingUrl, AsyncCallback<Void> asyncCallback);
void createSwissTimingArchiveConfiguration(String jsonUrl,
AsyncCallback<Void> asyncCallback);
void updateSwissTimingArchiveConfiguration(SwissTimingArchiveConfigurationWithSecurityDTO dto,
AsyncCallback<Void> asyncCallback);
void deleteSwissTimingArchiveConfiguration(SwissTimingArchiveConfigurationWithSecurityDTO dto,
AsyncCallback<Void> asyncCallback);
void updateRegatta(RegattaIdentifier regattaIdentifier, Date startDate, Date endDate, UUID defaultCourseAreaUuid,
RegattaConfigurationDTO regattaConfiguration, Double buoyZoneRadiusInHullLengths,
@@ -1022,6 +1040,13 @@ public interface SailingServiceAsync extends FileStorageManagementGwtServiceAsyn
void getAvailableDetailTypesForLeaderboard(String leaderboardName, RegattaAndRaceIdentifier raceOrNull,
AsyncCallback<Iterable<DetailType>> asyncCallback);
/**
* Checks whether the user may cut the race identified by {@code radeIdentifier} into multiple races. For this, it
* has to be found and it has to be a "smartphone-tracked" race with a valid start-of-tracking time. If not,
* {@code false} will be returned. If the user it not <em>permitted</em> to slice the race, e.g., because no
* permission has been granted to modify the leaderboard or regatta, an {@link AuthorizationException} will be
* thrown.
*/
void canSliceRace(RegattaAndRaceIdentifier raceIdentifier, AsyncCallback<Boolean> callback);
void sliceRace(RegattaAndRaceIdentifier raceIdentifier, String newRaceColumnName, TimePoint sliceFrom, TimePoint sliceTo,
@@ -1060,13 +1085,6 @@ public interface SailingServiceAsync extends FileStorageManagementGwtServiceAsyn
*/
void openRegattaRegistrationQrCode(String url, AsyncCallback<String> asyncCallback);
/**
* Sets the tenant that is used for CREATE operations done on this server
*
* @param tennant
*/
void setDefaultTenantForCurrentServer(String tennant, AsyncCallback<Void> asyncCallback);
/**
* gets a (possibly imcomplete) list of available tennants to choose from.
*/
@@ -1105,4 +1123,10 @@ public interface SailingServiceAsync extends FileStorageManagementGwtServiceAsyn
*/
void getCompetitor(UUID competitorId, String leaderboardName, String regattaRegistrationLinkSecret,
AsyncCallback<CompetitorDTO> asyncCallback);
void existsSwissTimingConfigurationForCurrentUser(String jsonUrl, AsyncCallback<Boolean> callback);
void existsSwissTimingArchiveConfigurationForCurrentUser(String jsonUrl, AsyncCallback<Boolean> callback);
void existsTracTracConfigurationForCurrentUser(String jsonUrl, AsyncCallback<Boolean> callback);
}
@@ -2243,4 +2243,19 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String invalidState();
String doYouReallyWantToRemoveDeviceConfiguration(String name);
String errorFetchingSimulationData(String message);
String addTracTracConnection();
String editTracTracConnection();
String filterTracTracConnections();
String creatorName();
String editSwissTimingAchivedConnection();
String addSwissTimingAchivedConnection();
String filterSwissTimingAchivedConnections();
String editSwissTimingConnections();
String filterSwissTimingConnections();
String updateURL();
String addSwissTimingConnection();
}
@@ -2246,4 +2246,15 @@ doYouReallyWantToRemoveDeviceConfiguration=Do you really want to remove device c
errorFetchingSimulationData=An Error occured while fetching simulation data: {0}
usernameAndPasswordMustBothBeSet=Username and password must both be filled or empty
explainUserName=Username to use for authentification on the master
explainPassword=Password to use for authentification on the master
explainPassword=Password to use for authentification on the master
addTracTracConnection=Add TracTrac Connection
editTracTracConnection=Edit TracTrac Connection
filterTracTracConnections=Filter TracTrac Connections
creatorName=Creator Name
addSwissTimingAchivedConnection=Add SwissTimingArchived Connection
editSwissTimingAchivedConnection=Edit SwissTimingArchived Connection
filterSwissTimingAchivedConnections=Filter SwissTimingArchived Connections
editSwissTimingConnections = Edit SwissTiming Connection
filterSwissTimingConnections=Filter SwissTiming Connections
addSwissTimingConnection=Add SwissTiming Connection
updateURL=Update URL
@@ -2238,4 +2238,15 @@ doYouReallyWantToRemoveDeviceConfiguration=Möchten Sie die Gerätekonfiguration
errorFetchingSimulationData=Ein Fehler ist beim Abrufen der Simulationsdaten aufgetreten: {0}
usernameAndPasswordMustBothBeSet=Username und Passwort müssen beide entweder gesetzt oder leer sein.
explainUserName=Der Benutzername der zur Authentifizierung auf dem Master eingesetzt wird
explainPassword=Das Password das zur Authentifizierung auf dem Master eingesetzt wird
explainPassword=Das Password das zur Authentifizierung auf dem Master eingesetzt wird
addTracTracConnection=TracTrac Verbindung hinzufügen
editTracTracConnection=TracTrac Verbindung bearbeiten
filterTracTracConnections=TracTrac Verbindungen filtern
creatorName=Name des Erstellers
addSwissTimingAchivedConnection=SwissTimingArchived Verbindung hinzufügen
editSwissTimingAchivedConnection=SwissTimingArchived Verbindung bearbeiten
filterSwissTimingAchivedConnections=SwissTimingArchived Verbindungen filtern
editSwissTimingConnections = SwissTiming Verbindung bearbeiten
filterSwissTimingConnections=SwissTiming Verbindungen filtern
addSwissTimingConnection=SwissTiming Verbindung hinzufügen
updateURL=Update URL
@@ -854,17 +854,28 @@ public abstract class LeaderboardPanel<LS extends LeaderboardSettings> extends A
// an overflow to the next column (see https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=3537)
setCellStyleNames(style.getTableresources().cellTableStyle().cellTableSailIdColumn());
}
/**
* Depending on the {@link LeaderboardDTO LeaderboardDTO's}
* {@link AbstractLeaderboardDTO#canBoatsOfCompetitorsChangePerRace canBoatsOfCompetitorsChangePerRace} flag
* there is a different precedence of showing either the sail ID (no changing boats) or the competitor short
* name (changing boats). This method handles the resolution of the text to show for a competitor in this column.
*/
private String getRealTextToShow(CompetitorDTO competitor) {
boolean preferSailId = !getLeaderboard().canBoatsOfCompetitorsChangePerRace;
return competitor.getShortInfo(preferSailId);
}
@Override
public InvertibleComparator<T> getComparator() {
return new InvertibleComparatorAdapter<T>() {
@Override
public int compare(T o1, T o2) {
return competitorFetcher.getCompetitor(o1).getShortInfo() == null
? competitorFetcher.getCompetitor(o2).getShortInfo() == null ? 0 : -1
: competitorFetcher.getCompetitor(o2).getShortInfo() == null ? 1
: Collator.getInstance().compare(competitorFetcher.getCompetitor(o1).getShortInfo(),
competitorFetcher.getCompetitor(o2).getShortInfo());
final String competitor1ShortInfo = getRealTextToShow(competitorFetcher.getCompetitor(o1));
final String competitor2ShortInfo = getRealTextToShow(competitorFetcher.getCompetitor(o2));
return competitor1ShortInfo == null ? competitor2ShortInfo == null ? 0 : -1
: competitor2ShortInfo == null ? 1
: Collator.getInstance().compare(competitor1ShortInfo, competitor2ShortInfo);
}
};
}
@@ -879,12 +890,12 @@ public abstract class LeaderboardPanel<LS extends LeaderboardSettings> extends A
public void render(Context context, T object, SafeHtmlBuilder sb) {
final CompetitorDTO competitor = competitorFetcher.getCompetitor(object);
LeaderboardPanel.this.renderCompetitorText(competitor, isShowCompetitorShortName(),
!isShowCompetitorFullName(), sb, builder -> builder.appendEscaped(competitor.getShortInfo()));
!isShowCompetitorFullName(), sb, builder -> builder.appendEscaped(getRealTextToShow(competitor)));
}
@Override
public String getValue(T object) {
return competitorFetcher.getCompetitor(object).getShortInfo();
return getRealTextToShow(competitorFetcher.getCompetitor(object));
}
}
@@ -43,6 +43,7 @@ import com.google.gwt.user.datepicker.client.DateBox;
import com.google.gwt.view.client.ListDataProvider;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO;
@@ -133,12 +134,23 @@ public class EditableLeaderboardPanel extends MultiRaceLeaderboardPanel {
super(new TextCell(), SortingOrder.ASCENDING, EditableLeaderboardPanel.this);
}
/**
* Depending on the {@link LeaderboardDTO LeaderboardDTO's}
* {@link AbstractLeaderboardDTO#canBoatsOfCompetitorsChangePerRace canBoatsOfCompetitorsChangePerRace} flag
* there is a different precedence of showing either the sail ID (no changing boats) or the competitor short
* name (changing boats). This method handles the resolution of the text to show for a competitor in this column.
*/
private String getRealTextToShow(CompetitorDTO competitor) {
boolean preferSailId = !getLeaderboard().canBoatsOfCompetitorsChangePerRace;
return competitor.getShortInfo(preferSailId);
}
@Override
public InvertibleComparator<CompetitorDTO> getComparator() {
return new InvertibleComparatorAdapter<CompetitorDTO>() {
@Override
public int compare(CompetitorDTO o1, CompetitorDTO o2) {
return Collator.getInstance().compare(o1.getShortInfo(), o2.getShortInfo());
return Collator.getInstance().compare(getRealTextToShow(o1), getRealTextToShow(o2));
}
};
}
@@ -161,7 +173,7 @@ public class EditableLeaderboardPanel extends MultiRaceLeaderboardPanel {
sb.append(FlagImageRenderer.image(flagImageResource.getSafeUri().asString()));
sb.appendHtmlConstant("&nbsp;");
}
sb.appendEscaped(object.getShortInfo());
sb.appendEscaped(getRealTextToShow(object));
}
@Override
@@ -454,12 +454,12 @@ import com.sap.sailing.gwt.ui.shared.SliceRacePreperationDTO;
import com.sap.sailing.gwt.ui.shared.SpeedWithBearingDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTOWithSecurity;
import com.sap.sailing.gwt.ui.shared.SwissTimingArchiveConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingArchiveConfigurationWithSecurityDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingConfigurationWithSecurityDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingEventRecordDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingRaceRecordDTO;
import com.sap.sailing.gwt.ui.shared.SwissTimingReplayRaceDTO;
import com.sap.sailing.gwt.ui.shared.TracTracConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.TracTracConfigurationWithSecurityDTO;
import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO;
import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO;
import com.sap.sailing.gwt.ui.shared.TypedDeviceMappingDTO;
@@ -1411,25 +1411,58 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
}
@Override
public List<TracTracConfigurationDTO> getPreviousTracTracConfigurations() throws Exception {
public List<TracTracConfigurationWithSecurityDTO> getPreviousTracTracConfigurations() throws Exception {
final Iterable<TracTracConfiguration> configs = tractracDomainObjectFactory.getTracTracConfigurations();
return getSecurityService().mapAndFilterByReadPermissionForCurrentUser(SecuredDomainType.TRACTRAC_ACCOUNT,
configs,
ttConfig -> new TracTracConfigurationDTO(ttConfig.getName(), ttConfig.getJSONURL().toString(),
ttConfig -> {
TracTracConfigurationWithSecurityDTO config = new TracTracConfigurationWithSecurityDTO(
ttConfig.getName(),
ttConfig.getJSONURL().toString(),
ttConfig.getLiveDataURI().toString(), ttConfig.getStoredDataURI().toString(),
ttConfig.getCourseDesignUpdateURI().toString(), ttConfig.getTracTracUsername().toString(),
ttConfig.getTracTracPassword().toString()));
ttConfig.getTracTracPassword().toString(), ttConfig.getCreatorName());
SecurityDTOUtil.addSecurityInformation(getSecurityService(), config, config.getIdentifier());
return config;
});
}
@Override
public void createTracTracConfiguration(String name, String jsonURL, String liveDataURI, String storedDataURI,
String courseDesignUpdateURI, String tracTracUsername, String tracTracPassword) throws Exception {
if (existsTracTracConfigurationForCurrentUser(jsonURL)) {
throw new RuntimeException("A configuration for the current user with this json URL already exists.");
}
final String currentUserName = getSecurityService().getCurrentUser().getName();
final TypeRelativeObjectIdentifier identifier = TracTracConfiguration.getTypeRelativeObjectIdentifier(jsonURL, currentUserName);
getSecurityService().setOwnershipCheckPermissionForObjectCreationAndRevertOnError(
SecuredDomainType.TRACTRAC_ACCOUNT,
identifier, name,
() -> tractracMongoObjectFactory.createTracTracConfiguration(
getTracTracAdapter().createTracTracConfiguration(currentUserName, name, jsonURL, liveDataURI,
storedDataURI,
courseDesignUpdateURI, tracTracUsername, tracTracPassword)));
}
@Override
public void storeTracTracConfiguration(String name, String jsonURL, String liveDataURI, String storedDataURI,
String courseDesignUpdateURI, String tracTracUsername, String tracTracPassword) throws Exception {
public void deleteTracTracConfiguration(TracTracConfigurationWithSecurityDTO tracTracConfiguration) {
getSecurityService().checkCurrentUserDeletePermission(tracTracConfiguration);
getSecurityService().checkPermissionAndDeleteOwnershipForObjectRemoval(tracTracConfiguration,
() ->
tractracMongoObjectFactory.deleteTracTracConfiguration(tracTracConfiguration.getCreatorName(),
tracTracConfiguration.getJsonUrl()));
}
getSecurityService().setOwnershipCheckPermissionForObjectCreationAndRevertOnError(
SecuredDomainType.TRACTRAC_ACCOUNT, TracTracConfiguration.getTypeRelativeObjectIdentifier(jsonURL), name,
() -> tractracMongoObjectFactory.storeTracTracConfiguration(
getTracTracAdapter().createTracTracConfiguration(name, jsonURL, liveDataURI, storedDataURI,
courseDesignUpdateURI, tracTracUsername, tracTracPassword)));
@Override
public void updateTracTracConfiguration(TracTracConfigurationWithSecurityDTO tracTracConfiguration)
throws Exception {
getSecurityService().checkCurrentUserUpdatePermission(tracTracConfiguration);
tractracMongoObjectFactory.updateTracTracConfiguration(
getTracTracAdapter().createTracTracConfiguration(tracTracConfiguration.getCreatorName(),
tracTracConfiguration.getName(), tracTracConfiguration.getJsonUrl(),
tracTracConfiguration.getLiveDataURI(), tracTracConfiguration.getStoredDataURI(),
tracTracConfiguration.getCourseDesignUpdateURI(), tracTracConfiguration.getTracTracUsername(),
tracTracConfiguration.getTracTracPassword()));
}
private RaceDefinition getRaceByName(Regatta regatta, String raceName) {
@@ -3144,12 +3177,96 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
}
@Override
public List<SwissTimingConfigurationDTO> getPreviousSwissTimingConfigurations() {
public boolean existsSwissTimingConfigurationForCurrentUser(String jsonUrl)
throws Exception, UnauthorizedException {
boolean found = false;
final String currentUserName = getSecurityService().getCurrentUser().getName();
for (final SwissTimingConfigurationWithSecurityDTO dto : getPreviousSwissTimingConfigurations()) {
if (dto.getJsonUrl().equals(jsonUrl) && currentUserName.equals(dto.getCreatorName())) {
found = true;
break;
}
}
return found;
}
@Override
public boolean existsSwissTimingArchiveConfigurationForCurrentUser(String jsonUrl)
throws Exception, UnauthorizedException {
boolean found = false;
final String currentUserName = getSecurityService().getCurrentUser().getName();
for (final SwissTimingArchiveConfigurationWithSecurityDTO dto : getPreviousSwissTimingArchiveConfigurations()) {
if (dto.getJsonUrl().equals(jsonUrl) && currentUserName.equals(dto.getCreatorName())) {
found = true;
break;
}
}
return found;
}
@Override
public boolean existsTracTracConfigurationForCurrentUser(String jsonUrl) throws Exception, UnauthorizedException {
boolean found = false;
final String currentUserName = getSecurityService().getCurrentUser().getName();
for (final TracTracConfigurationWithSecurityDTO dto : getPreviousTracTracConfigurations()) {
if (dto.getJsonUrl().equals(jsonUrl) && currentUserName.equals(dto.getCreatorName())) {
found = true;
break;
}
}
return found;
}
@Override
public List<SwissTimingConfigurationWithSecurityDTO> getPreviousSwissTimingConfigurations() {
Iterable<SwissTimingConfiguration> configs = swissTimingAdapterPersistence.getSwissTimingConfigurations();
return getSecurityService().mapAndFilterByReadPermissionForCurrentUser(SecuredDomainType.SWISS_TIMING_ACCOUNT, configs,
stConfig -> new SwissTimingConfigurationDTO(stConfig.getName(), stConfig.getJsonURL(),
stConfig -> {
final SwissTimingConfigurationWithSecurityDTO config = new SwissTimingConfigurationWithSecurityDTO(
stConfig.getName(), stConfig.getJsonURL(),
stConfig.getHostname(), stConfig.getPort(), stConfig.getUpdateURL(),
stConfig.getUpdateUsername(), stConfig.getUpdatePassword()));
stConfig.getUpdateUsername(), stConfig.getUpdatePassword(), stConfig.getCreatorName());
SecurityDTOUtil.addSecurityInformation(getSecurityService(), config, config.getIdentifier());
return config;
});
}
@Override
public void createSwissTimingConfiguration(String configName, String jsonURL, String hostname, Integer port,
String updateURL, String updateUsername, String updatePassword) throws Exception {
if (!jsonURL.equalsIgnoreCase("test")) {
if (existsSwissTimingConfigurationForCurrentUser(jsonURL)) {
throw new RuntimeException("A Configuration for the current user with this json URL already exists.");
}
final String currentUserName = getSecurityService().getCurrentUser().getName();
final TypeRelativeObjectIdentifier identifier = SwissTimingConfiguration.getTypeRelativeObjectIdentifier(jsonURL, currentUserName);
getSecurityService().setOwnershipCheckPermissionForObjectCreationAndRevertOnError(
SecuredDomainType.SWISS_TIMING_ACCOUNT,
identifier, configName,
() -> swissTimingAdapterPersistence
.createSwissTimingConfiguration(
swissTimingFactory.createSwissTimingConfiguration(configName,
jsonURL, hostname, port, updateURL, updateUsername, updatePassword,
currentUserName)));
}
}
@Override
public void deleteSwissTimingConfiguration(SwissTimingConfigurationWithSecurityDTO configuration) {
getSecurityService().checkCurrentUserDeletePermission(configuration);
getSecurityService().checkPermissionAndDeleteOwnershipForObjectRemoval(configuration,
() -> swissTimingAdapterPersistence.deleteSwissTimingConfiguration(configuration.getCreatorName(),
configuration.getJsonUrl()));
}
@Override
public void updateSwissTimingConfiguration(SwissTimingConfigurationWithSecurityDTO configuration)
throws Exception {
getSecurityService().checkCurrentUserUpdatePermission(configuration);
swissTimingAdapterPersistence.updateSwissTimingConfiguration(swissTimingFactory.createSwissTimingConfiguration(
configuration.getName(), configuration.getJsonUrl(), configuration.getHostname(),
configuration.getPort(), configuration.getUpdateURL(), configuration.getUpdateUsername(),
configuration.getUpdatePassword(), configuration.getCreatorName()));
}
@Override
@@ -3183,19 +3300,6 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
}
return result;
}
@Override
public void storeSwissTimingConfiguration(String configName, String jsonURL, String hostname, Integer port,
String updateURL, String updateUsername, String updatePassword) throws Exception {
if (!jsonURL.equalsIgnoreCase("test")) {
getSecurityService().setOwnershipCheckPermissionForObjectCreationAndRevertOnError(
SecuredDomainType.SWISS_TIMING_ACCOUNT, SwissTimingConfiguration.getTypeRelativeObjectIdentifier(jsonURL),
configName,
() -> swissTimingAdapterPersistence
.storeSwissTimingConfiguration(swissTimingFactory.createSwissTimingConfiguration(configName,
jsonURL, hostname, port, updateURL, updateUsername, updatePassword)));
}
}
private RaceLogStore getRaceLogStore() {
return MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStore(mongoObjectFactory,
@@ -4237,32 +4341,6 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
});
}
@Override
public List<EventBaseDTO> getPublicEventsOfAllSailingServers() throws MalformedURLException {
List<EventBaseDTO> result = new ArrayList<>();
for (EventDTO localEvent : getEvents()) {
if (localEvent.isPublic) {
result.add(localEvent);
}
}
for (Entry<RemoteSailingServerReference, com.sap.sse.common.Util.Pair<Iterable<EventBase>, Exception>> serverRefAndEventsOrException :
getService().getPublicEventsOfAllSailingServers().entrySet()) {
final com.sap.sse.common.Util.Pair<Iterable<EventBase>, Exception> eventsOrException = serverRefAndEventsOrException.getValue();
final RemoteSailingServerReference serverRef = serverRefAndEventsOrException.getKey();
final Iterable<EventBase> remoteEvents = eventsOrException.getA();
String baseURLFromServerReference = getBaseURL(serverRef.getURL()).toString();
if (remoteEvents != null) {
for (EventBase remoteEvent : remoteEvents) {
EventBaseDTO remoteEventDTO = convertToEventDTO(remoteEvent);
remoteEventDTO.setBaseURL(remoteEvent.getBaseURL() == null ? baseURLFromServerReference : remoteEvent.getBaseURL().toString());
remoteEventDTO.setIsOnRemoteServer(true);
result.add(remoteEventDTO);
}
}
}
return result;
}
/**
* Determines the base URL (protocol, host and port parts) used for the currently executing servlet request. Defaults
* to <code>http://sapsailing.com</code>.
@@ -5183,20 +5261,50 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
}
@Override
public List<SwissTimingArchiveConfigurationDTO> getPreviousSwissTimingArchiveConfigurations() {
public List<SwissTimingArchiveConfigurationWithSecurityDTO> getPreviousSwissTimingArchiveConfigurations() {
Iterable<SwissTimingArchiveConfiguration> configs = swissTimingAdapterPersistence
.getSwissTimingArchiveConfigurations();
return getSecurityService().mapAndFilterByReadPermissionForCurrentUser(
SecuredDomainType.SWISS_TIMING_ARCHIVE_ACCOUNT, configs,
stArchiveConfig -> new SwissTimingArchiveConfigurationDTO(stArchiveConfig.getJsonURL()));
stArchiveConfig -> {
SwissTimingArchiveConfigurationWithSecurityDTO config = new SwissTimingArchiveConfigurationWithSecurityDTO(
stArchiveConfig.getJsonURL(), stArchiveConfig.getCreatorName());
SecurityDTOUtil.addSecurityInformation(getSecurityService(), config, config.getIdentifier());
return config;
});
}
@Override
public void storeSwissTimingArchiveConfiguration(String swissTimingJsonUrl) throws Exception {
public void createSwissTimingArchiveConfiguration(final String jsonURL)
throws Exception {
if (existsSwissTimingArchiveConfigurationForCurrentUser(jsonURL)) {
throw new RuntimeException("A configuration for the current user with this json URL already exists.");
}
final String currentUserName = getSecurityService().getCurrentUser().getName();
final TypeRelativeObjectIdentifier identifier = SwissTimingArchiveConfiguration
.getTypeRelativeObjectIdentifier(jsonURL, currentUserName);
getSecurityService().setOwnershipCheckPermissionForObjectCreationAndRevertOnError(
SecuredDomainType.SWISS_TIMING_ACCOUNT, SwissTimingArchiveConfiguration.getTypeRelativeObjectIdentifier(swissTimingJsonUrl),
swissTimingJsonUrl, () -> swissTimingAdapterPersistence.storeSwissTimingArchiveConfiguration(
swissTimingFactory.createSwissTimingArchiveConfiguration(swissTimingJsonUrl)));
SecuredDomainType.SWISS_TIMING_ARCHIVE_ACCOUNT,
identifier, identifier.toString(),
() -> swissTimingAdapterPersistence.createSwissTimingArchiveConfiguration(
swissTimingFactory.createSwissTimingArchiveConfiguration(jsonURL,
currentUserName)));
}
@Override
public void updateSwissTimingArchiveConfiguration(SwissTimingArchiveConfigurationWithSecurityDTO dto)
throws Exception {
getSecurityService().checkCurrentUserUpdatePermission(dto);
swissTimingAdapterPersistence.updateSwissTimingArchiveConfiguration(
swissTimingFactory.createSwissTimingArchiveConfiguration(dto.getJsonUrl(), dto.getCreatorName()));
}
@Override
public void deleteSwissTimingArchiveConfiguration(SwissTimingArchiveConfigurationWithSecurityDTO dto)
throws Exception {
getSecurityService().checkPermissionAndDeleteOwnershipForObjectRemoval(dto.getIdentifier(),
() -> swissTimingAdapterPersistence.deleteSwissTimingArchiveConfiguration(swissTimingFactory
.createSwissTimingArchiveConfiguration(dto.getJsonUrl(), dto.getCreatorName())));
}
protected com.sap.sailing.domain.base.DomainFactory getBaseDomainFactory() {
@@ -6366,7 +6474,8 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
final com.sap.sailing.domain.igtimiadapter.User user = igtimiAccount.getUser();
final String email = user.getEmail();
final String name = user.getFirstName() + " " + user.getSurname();
final AccountWithSecurityDTO securedAccount = new AccountWithSecurityDTO(email, name);
final AccountWithSecurityDTO securedAccount = new AccountWithSecurityDTO(email, name,
igtimiAccount.getCreatorName());
SecurityDTOUtil.addSecurityInformation(getSecurityService(), securedAccount, igtimiAccount.getIdentifier());
return securedAccount;
}
@@ -6393,10 +6502,12 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
final Account existingAccount = getIgtimiConnectionFactory().getExistingAccountByEmail(eMailAddress);
final Account account;
if (existingAccount == null) {
final String creatorName = getSecurityService().getCurrentUser().getName();
account = getSecurityService().setOwnershipCheckPermissionForObjectCreationAndRevertOnError(
SecuredDomainType.IGTIMI_ACCOUNT, Account.getTypeRelativeObjectIdentifier(eMailAddress),
SecuredDomainType.IGTIMI_ACCOUNT,
Account.getTypeRelativeObjectIdentifier(eMailAddress, creatorName),
eMailAddress,
() -> getIgtimiConnectionFactory().createAccountToAccessUserData(eMailAddress, password));
() -> getIgtimiConnectionFactory().createAccountToAccessUserData(creatorName, eMailAddress, password));
} else {
logger.warning("Igtimi account "+eMailAddress+" already exists.");
account = null; // account with that e-mail already exists
@@ -9015,22 +9126,6 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
return result;
}
@Override
public void setDefaultTenantForCurrentServer(String tennant) {
User user = getSecurityService().getCurrentUser();
if (user != null) {
if (tennant == null) {
user.getDefaultTenantMap().remove(ServerInfo.getName());
} else {
UserGroup userGroupCandidate = getSecurityService().getUserGroupByName(tennant);
// FIXME allow admins to pose as any tenant?
if (Util.contains(user.getUserGroups(), userGroupCandidate)) {
user.getDefaultTenantMap().put(ServerInfo.getName(), userGroupCandidate);
}
}
}
}
@Override
public List<String> getPossibleTennants() {
// FIXME this is most likely greatly oversimplified and needs to be improved
@@ -15,10 +15,12 @@ public class AccountWithSecurityDTO implements SecuredDTO {
private String email;
private String name;
private String creatorName;
public AccountWithSecurityDTO(String email, String name) {
public AccountWithSecurityDTO(String email, String name, String creatorName) {
this.email = email;
this.name = name;
this.creatorName = creatorName;
}
public AccountWithSecurityDTO() {
@@ -36,6 +38,10 @@ public class AccountWithSecurityDTO implements SecuredDTO {
return email;
}
public String getCreatorName() {
return creatorName;
}
@Override
public AccessControlListDTO getAccessControlList() {
return securityInformation.getAccessControlList();
@@ -66,13 +72,14 @@ public class AccountWithSecurityDTO implements SecuredDTO {
return name;
}
private TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier() {
return creatorName == null ? new TypeRelativeObjectIdentifier(email)
: new TypeRelativeObjectIdentifier(email, creatorName);
}
@Override
public QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
}
private TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier() {
return new TypeRelativeObjectIdentifier(email);
}
}
@@ -1,18 +0,0 @@
package com.sap.sailing.gwt.ui.shared;
import com.google.gwt.user.client.rpc.IsSerializable;
public class SwissTimingArchiveConfigurationDTO implements IsSerializable {
private String jsonUrl;
SwissTimingArchiveConfigurationDTO() {}
public SwissTimingArchiveConfigurationDTO(String jsonUrl) {
super();
this.jsonUrl = jsonUrl;
}
public String getJsonUrl() {
return jsonUrl;
}
}
@@ -0,0 +1,80 @@
package com.sap.sailing.gwt.ui.shared;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sse.security.shared.HasPermissions;
import com.sap.sse.security.shared.QualifiedObjectIdentifier;
import com.sap.sse.security.shared.TypeRelativeObjectIdentifier;
import com.sap.sse.security.shared.dto.AccessControlListDTO;
import com.sap.sse.security.shared.dto.OwnershipDTO;
import com.sap.sse.security.shared.dto.SecuredDTO;
import com.sap.sse.security.shared.dto.SecurityInformationDTO;
public class SwissTimingArchiveConfigurationWithSecurityDTO implements IsSerializable, SecuredDTO {
private static final long serialVersionUID = 108023338445751985L;
private String jsonUrl;
private String creatorName;
private SecurityInformationDTO securityInformation = new SecurityInformationDTO();
public SwissTimingArchiveConfigurationWithSecurityDTO() {
}
public SwissTimingArchiveConfigurationWithSecurityDTO(String jsonUrl, String creatorName) {
super();
this.jsonUrl = jsonUrl;
this.creatorName = creatorName;
}
public String getJsonUrl() {
return jsonUrl;
}
public String getCreatorName() {
return creatorName;
}
@Override
public AccessControlListDTO getAccessControlList() {
return securityInformation.getAccessControlList();
}
@Override
public OwnershipDTO getOwnership() {
return securityInformation.getOwnership();
}
@Override
public void setAccessControlList(AccessControlListDTO createAccessControlListDTO) {
securityInformation.setAccessControlList(createAccessControlListDTO);
}
@Override
public void setOwnership(OwnershipDTO createOwnershipDTO) {
securityInformation.setOwnership(createOwnershipDTO);
}
public SecurityInformationDTO getSecurityInformation() {
return securityInformation;
}
@Override
public String getName() {
return jsonUrl;
}
@Override
public HasPermissions getType() {
return SecuredDomainType.SWISS_TIMING_ARCHIVE_ACCOUNT;
}
private TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier() {
return creatorName == null ? new TypeRelativeObjectIdentifier(jsonUrl)
: new TypeRelativeObjectIdentifier(jsonUrl, creatorName);
}
@Override
public QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
}
}
@@ -1,67 +0,0 @@
package com.sap.sailing.gwt.ui.shared;
import com.google.gwt.user.client.rpc.IsSerializable;
public class SwissTimingConfigurationDTO implements IsSerializable {
private String name;
private String jsonURL;
private String hostname;
private Integer port;
/**
* The URL to use for updates fed back to the SwissTiming server, such as changes of race timings
* or course updates.
*/
private String updateURL;
/**
* The username to use as part of the credentials for requests to the {@link #updateURL}.
*/
private String updateUsername;
/**
* The password to use as part of the credentials for requests to the {@link #updateURL}.
*/
private String updatePassword;
public SwissTimingConfigurationDTO() {}
public SwissTimingConfigurationDTO(String name, String jsonURL, String hostname, Integer port, String updateURL, String updateUsername, String updatePassword) {
super();
this.name = name;
this.jsonURL = jsonURL;
this.hostname = hostname;
this.port = port;
this.updateURL = updateURL;
this.updateUsername = updateUsername;
this.updatePassword = updatePassword;
}
public String getName() {
return name;
}
public String getJsonURL() {
return jsonURL;
}
public String getHostname() {
return hostname;
}
public Integer getPort() {
return port;
}
public String getUpdateURL() {
return updateURL;
}
public String getUpdateUsername() {
return updateUsername;
}
public String getUpdatePassword() {
return updatePassword;
}
}
@@ -0,0 +1,133 @@
package com.sap.sailing.gwt.ui.shared;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sse.security.shared.HasPermissions;
import com.sap.sse.security.shared.QualifiedObjectIdentifier;
import com.sap.sse.security.shared.TypeRelativeObjectIdentifier;
import com.sap.sse.security.shared.dto.AccessControlListDTO;
import com.sap.sse.security.shared.dto.OwnershipDTO;
import com.sap.sse.security.shared.dto.SecuredDTO;
import com.sap.sse.security.shared.dto.SecurityInformationDTO;
public class SwissTimingConfigurationWithSecurityDTO implements IsSerializable, SecuredDTO {
private static final long serialVersionUID = 8896994399042048620L;
private SecurityInformationDTO securityInformation = new SecurityInformationDTO();
private String name;
private String jsonUrl;
private String hostname;
private Integer port;
private String creatorName;
/**
* The URL to use for updates fed back to the SwissTiming server, such as changes of race timings
* or course updates.
*/
private String updateURL;
/**
* The username to use as part of the credentials for requests to the {@link #updateURL}.
*/
private String updateUsername;
/**
* The password to use as part of the credentials for requests to the {@link #updateURL}.
*/
private String updatePassword;
public SwissTimingConfigurationWithSecurityDTO() {}
public SwissTimingConfigurationWithSecurityDTO(String name, String jsonUrl, String hostname, Integer port,
String updateURL, String updateUsername, String updatePassword, String creatorName) {
super();
this.name = name;
this.jsonUrl = jsonUrl;
this.hostname = hostname;
this.port = port;
this.updateURL = updateURL;
this.updateUsername = updateUsername;
this.updatePassword = updatePassword;
this.creatorName = creatorName;
}
public SwissTimingConfigurationWithSecurityDTO(SwissTimingConfigurationWithSecurityDTO dto, String hostname,
Integer port, String eventName) {
this.name = eventName;
this.jsonUrl = dto.getJsonUrl();
this.hostname = hostname;
this.port = port;
this.updateURL = dto.getUpdateURL();
this.updateUsername = dto.getUpdateUsername();
this.updatePassword = dto.getUpdatePassword();
this.creatorName = dto.getCreatorName();
}
public String getName() {
return name;
}
public String getJsonUrl() {
return jsonUrl;
}
public String getHostname() {
return hostname;
}
public Integer getPort() {
return port;
}
public String getUpdateURL() {
return updateURL;
}
public String getUpdateUsername() {
return updateUsername;
}
public String getUpdatePassword() {
return updatePassword;
}
public String getCreatorName() {
return creatorName;
}
@Override
public AccessControlListDTO getAccessControlList() {
return securityInformation.getAccessControlList();
}
@Override
public OwnershipDTO getOwnership() {
return securityInformation.getOwnership();
}
@Override
public void setAccessControlList(AccessControlListDTO createAccessControlListDTO) {
securityInformation.setAccessControlList(createAccessControlListDTO);
}
@Override
public void setOwnership(OwnershipDTO createOwnershipDTO) {
securityInformation.setOwnership(createOwnershipDTO);
}
@Override
public HasPermissions getType() {
return SecuredDomainType.SWISS_TIMING_ACCOUNT;
}
private TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier() {
return creatorName == null ? new TypeRelativeObjectIdentifier(jsonUrl)
: new TypeRelativeObjectIdentifier(jsonUrl, creatorName);
}
@Override
public QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
}
}
@@ -1,28 +0,0 @@
package com.sap.sailing.gwt.ui.shared;
import com.google.gwt.user.client.rpc.IsSerializable;
public class TracTracConfigurationDTO implements IsSerializable {
public String name;
public String jsonURL;
public String liveDataURI;
public String storedDataURI;
public String courseDesignUpdateURI;
public String tractracUsername;
public String tractracPassword;
public TracTracConfigurationDTO() {}
public TracTracConfigurationDTO(String name, String jsonURL, String liveDataURI, String storedDataURI, String courseDesignUpdateUrl,
String tractracUsername, String tractracPassword) {
super();
this.name = name;
this.jsonURL = jsonURL;
this.liveDataURI = liveDataURI;
this.storedDataURI = storedDataURI;
this.courseDesignUpdateURI = courseDesignUpdateUrl;
this.tractracUsername = tractracUsername;
this.tractracPassword = tractracPassword;
}
}
@@ -0,0 +1,126 @@
package com.sap.sailing.gwt.ui.shared;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sse.security.shared.HasPermissions;
import com.sap.sse.security.shared.QualifiedObjectIdentifier;
import com.sap.sse.security.shared.TypeRelativeObjectIdentifier;
import com.sap.sse.security.shared.dto.AccessControlListDTO;
import com.sap.sse.security.shared.dto.OwnershipDTO;
import com.sap.sse.security.shared.dto.SecuredDTO;
import com.sap.sse.security.shared.dto.SecurityInformationDTO;
public class TracTracConfigurationWithSecurityDTO implements IsSerializable, SecuredDTO {
private static final long serialVersionUID = -3567107321280535272L;
private SecurityInformationDTO securityInformation = new SecurityInformationDTO();
private String name;
private String jsonUrl;
private String liveDataURI;
private String storedDataURI;
private String courseDesignUpdateURI;
private String tracTracUsername;
private String tracTracPassword;
private String creatorName;
public TracTracConfigurationWithSecurityDTO() {
}
public TracTracConfigurationWithSecurityDTO(String name, String jsonUrl, String liveDataURI, String storedDataURI,
String courseDesignUpdateUrl, String tractracUsername, String tractracPassword, String creatorName) {
super();
this.name = name;
this.jsonUrl = jsonUrl;
this.liveDataURI = liveDataURI;
this.storedDataURI = storedDataURI;
this.courseDesignUpdateURI = courseDesignUpdateUrl;
this.tracTracUsername = tractracUsername;
this.tracTracPassword = tractracPassword;
this.creatorName = creatorName;
}
/** Copy constructor with new name */
public TracTracConfigurationWithSecurityDTO(TracTracConfigurationWithSecurityDTO config, final String name) {
super();
this.name = name;
this.jsonUrl = config.getJsonUrl();
this.liveDataURI = config.getLiveDataURI();
this.storedDataURI = config.getStoredDataURI();
this.courseDesignUpdateURI = config.getCourseDesignUpdateURI();
this.tracTracUsername = config.getTracTracUsername();
this.tracTracPassword = config.getTracTracPassword();
this.creatorName = config.getCreatorName();
}
@Override
public AccessControlListDTO getAccessControlList() {
return securityInformation.getAccessControlList();
}
@Override
public OwnershipDTO getOwnership() {
return securityInformation.getOwnership();
}
@Override
public void setAccessControlList(AccessControlListDTO createAccessControlListDTO) {
securityInformation.setAccessControlList(createAccessControlListDTO);
}
@Override
public void setOwnership(OwnershipDTO createOwnershipDTO) {
securityInformation.setOwnership(createOwnershipDTO);
}
public SecurityInformationDTO getSecurityInformation() {
return securityInformation;
}
public String getName() {
return name;
}
public String getJsonUrl() {
return jsonUrl;
}
public String getLiveDataURI() {
return liveDataURI;
}
public String getStoredDataURI() {
return storedDataURI;
}
public String getCourseDesignUpdateURI() {
return courseDesignUpdateURI;
}
public String getTracTracUsername() {
return tracTracUsername;
}
public String getTracTracPassword() {
return tracTracPassword;
}
public String getCreatorName() {
return creatorName;
}
@Override
public HasPermissions getType() {
return SecuredDomainType.TRACTRAC_ACCOUNT;
}
private TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier() {
return creatorName == null ? new TypeRelativeObjectIdentifier(jsonUrl)
: new TypeRelativeObjectIdentifier(jsonUrl, creatorName);
}
@Override
public QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
}
}
@@ -55,11 +55,13 @@ import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartProcedureChanged
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartTimeEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogWindFixEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.SimpleRaceLogIdentifierImpl;
import com.sap.sailing.domain.base.Boat;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CourseBase;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.impl.BoatClassImpl;
import com.sap.sailing.domain.common.CourseDesignerMode;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.Wind;
@@ -307,9 +309,12 @@ public class TestStoringAndRetrievingRaceLogInLeaderboards extends RaceLogMongoD
Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", "SAP",
Color.RED, "someone@nowhere.de", null, null,
/* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null);
Boat storedBoat = DomainFactory.INSTANCE.getOrCreateBoat(UUID.randomUUID(), "SAP Extreme Sailing Team",
new BoatClassImpl("X40", false), "123", Color.RED);
CompetitorResults storedPositioningList = new CompetitorResultsImpl();
storedPositioningList.add(new CompetitorResultImpl(storedCompetitor.getId(), storedCompetitor.getName(), /* rank */
1, MaxPointsReason.NONE, /* score */ null, /* finishingTimePoint */null, /* comment */null, MergeState.OK));
storedPositioningList.add(new CompetitorResultImpl(storedCompetitor.getId(), storedCompetitor.getName(), storedCompetitor.getShortName(),
storedBoat.getName(), storedBoat.getSailID(), /* rank */ 1, MaxPointsReason.NONE, /* score */ null, /* finishingTimePoint */null,
/* comment */null, MergeState.OK));
RaceLogFinishPositioningConfirmedEvent event = new RaceLogFinishPositioningConfirmedEventImpl(now, author, 0, storedPositioningList);
addAndStoreRaceLogEvent(leaderboard, raceColumnName, event);
RaceLog loadedRaceLog = retrieveRaceLog();
@@ -331,11 +336,14 @@ public class TestStoringAndRetrievingRaceLogInLeaderboards extends RaceLogMongoD
Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", "SAP",
Color.RED, "someone@nowhere.de", null, null,
/* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null);
Boat storedBoat = DomainFactory.INSTANCE.getOrCreateBoat(UUID.randomUUID(), "SAP Extreme Sailing Team",
new BoatClassImpl("X40", false), "123", Color.RED);
CompetitorResults storedPositioningList = new CompetitorResultsImpl();
final TimePoint finishingTimePoint = MillisecondsTimePoint.now();
final String comment = "This is a comment";
storedPositioningList.add(new CompetitorResultImpl(storedCompetitor.getId(), storedCompetitor.getName(), /* rank */
1, MaxPointsReason.NONE, /* score */ 12.3, finishingTimePoint, comment, MergeState.OK));
storedPositioningList.add(new CompetitorResultImpl(storedCompetitor.getId(), storedCompetitor.getName(), storedCompetitor.getShortName(),
storedBoat.getName(), storedBoat.getSailID(), /* rank */ 1, MaxPointsReason.NONE, /* score */ 12.3, finishingTimePoint, comment,
MergeState.OK));
RaceLogFinishPositioningConfirmedEvent event = new RaceLogFinishPositioningConfirmedEventImpl(now, author, 0, storedPositioningList);
addAndStoreRaceLogEvent(leaderboard, raceColumnName, event);
RaceLog loadedRaceLog = retrieveRaceLog();
@@ -399,9 +407,12 @@ public class TestStoringAndRetrievingRaceLogInLeaderboards extends RaceLogMongoD
public void testStoreAndRetrieveSimpleLeaderboardWithRaceLogFinishPositioningListChangeEvent() {
Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", "SAP", Color.RED,
"someone@nowhere.de", null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null);
Boat storedBoat = DomainFactory.INSTANCE.getOrCreateBoat(UUID.randomUUID(), "SAP Extreme Sailing Team",
new BoatClassImpl("X40", false), "123", Color.RED);
CompetitorResults storedPositioningList = new CompetitorResultsImpl();
storedPositioningList.add(new CompetitorResultImpl(storedCompetitor.getId(), storedCompetitor.getName(), /* rank */
1, MaxPointsReason.NONE, /* score */ null, /* finishingTimePoint */null, /* comment */null, MergeState.OK));
storedPositioningList.add(new CompetitorResultImpl(storedCompetitor.getId(), storedCompetitor.getName(), storedCompetitor.getShortName(),
storedBoat.getName(), storedBoat.getSailID(), /* rank */ 1, MaxPointsReason.NONE, /* score */ null, /* finishingTimePoint */null,
/* comment */null, MergeState.OK));
RaceLogFinishPositioningListChangedEvent event = new RaceLogFinishPositioningListChangedEventImpl(now, author, 0, storedPositioningList);
addAndStoreRaceLogEvent(leaderboard, raceColumnName, event);
@@ -43,6 +43,7 @@ import com.sap.sailing.domain.abstractlog.race.impl.RaceLogRaceStatusEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartProcedureChangedEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartTimeEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogWindFixEventImpl;
import com.sap.sailing.domain.base.Boat;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CourseBase;
import com.sap.sailing.domain.base.DomainFactory;
@@ -50,6 +51,7 @@ import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.impl.BoatClassImpl;
import com.sap.sailing.domain.base.impl.FleetImpl;
import com.sap.sailing.domain.common.CourseDesignerMode;
import com.sap.sailing.domain.common.MaxPointsReason;
@@ -207,8 +209,12 @@ public class TestStoringAndRetrievingRaceLogInRegatta extends AbstractTestStorin
public void testStoreAndRetrieveRegattaWithRaceLogFinishPositioningListChangeEvent() {
Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", "SAP", Color.RED,
"someone@nowhere.de", null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null);
Boat storedBoat = DomainFactory.INSTANCE.getOrCreateBoat(UUID.randomUUID(), "SAP Extreme Sailing Team",
new BoatClassImpl("X40", false), "123", Color.RED);
CompetitorResults storedPositioningList = new CompetitorResultsImpl();
storedPositioningList.add(new CompetitorResultImpl(storedCompetitor.getId(), storedCompetitor.getName(), /* rank */ 1, MaxPointsReason.NONE, /* score */ null, /* finishingTime */ null, /* comment */ null, MergeState.OK));
storedPositioningList.add(new CompetitorResultImpl(storedCompetitor.getId(), storedCompetitor.getName(), storedCompetitor.getShortName(),
storedBoat.getName(), storedBoat.getSailID(), /* rank */ 1, MaxPointsReason.NONE, /* score */ null, /* finishingTime */ null,
/* comment */ null, MergeState.OK));
RaceLogFinishPositioningListChangedEvent event = new RaceLogFinishPositioningListChangedEventImpl(now, author, 0, storedPositioningList);
addAndStoreRaceLogEvent(regatta, raceColumnName, event);
RaceLog loadedRaceLog = retrieveRaceLog();
@@ -231,8 +237,12 @@ public class TestStoringAndRetrievingRaceLogInRegatta extends AbstractTestStorin
public void testStoreAndRetrieveRegattaWithRaceLogFinishPositioningConfirmedEvent() {
Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", "SAP", Color.RED,
"someone@nowhere.de", null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null);
Boat storedBoat = DomainFactory.INSTANCE.getOrCreateBoat(UUID.randomUUID(), "SAP Extreme Sailing Team",
new BoatClassImpl("X40", false), "123", Color.RED);
CompetitorResults storedPositioningList = new CompetitorResultsImpl();
storedPositioningList.add(new CompetitorResultImpl(storedCompetitor.getId(), storedCompetitor.getName(), /* rank */ 1, MaxPointsReason.NONE, /* score */ null, /* finishingTime */ null, /* comment */ null, MergeState.OK));
storedPositioningList.add(new CompetitorResultImpl(storedCompetitor.getId(), storedCompetitor.getName(), storedCompetitor.getShortName(),
storedBoat.getName(), storedBoat.getSailID(), /* rank */ 1, MaxPointsReason.NONE, /* score */ null, /* finishingTime */ null,
/* comment */ null, MergeState.OK));
RaceLogFinishPositioningConfirmedEvent event = new RaceLogFinishPositioningConfirmedEventImpl(now, author, 0, storedPositioningList);
addAndStoreRaceLogEvent(regatta, raceColumnName, event);
RaceLog loadedRaceLog = retrieveRaceLog();
@@ -0,0 +1,30 @@
package com.sap.sailing.selenium.pages.adminconsole.tractrac;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import com.sap.sailing.selenium.core.BySeleniumId;
import com.sap.sailing.selenium.core.FindBy;
import com.sap.sailing.selenium.pages.common.DataEntryDialogPO;
public class AddTracTracConnectionDialogPO extends DataEntryDialogPO {
// TODO [D049941]: Prefix the Ids with the component (e.g. "Button")
@FindBy(how = BySeleniumId.class, using = "LiveURITextBox")
private WebElement liveURITextBox;
@FindBy(how = BySeleniumId.class, using = "StoredURITextBox")
private WebElement storedURITextBox;
@FindBy(how = BySeleniumId.class, using = "JsonURLTextBox")
private WebElement jsonURLTextBox;
protected AddTracTracConnectionDialogPO(WebDriver driver, WebElement element) {
super(driver, element);
}
public void setJsonUrl(String jsonUrl) {
jsonURLTextBox.clear();
jsonURLTextBox.sendKeys(jsonUrl);
}
}
@@ -74,16 +74,6 @@ public class TracTracEventManagementPanelPO extends PageArea {
}
}
// TODO [D049941]: Prefix the Ids with the component (e.g. "Button")
@FindBy(how = BySeleniumId.class, using = "LiveURITextBox")
private WebElement liveURITextBox;
@FindBy(how = BySeleniumId.class, using = "StoredURITextBox")
private WebElement storedURITextBox;
@FindBy(how = BySeleniumId.class, using = "JsonURLTextBox")
private WebElement jsonURLTextBox;
@FindBy(how = BySeleniumId.class, using = "ListRacesButton")
private WebElement listRacesButton;
@@ -111,6 +101,12 @@ public class TracTracEventManagementPanelPO extends PageArea {
@FindBy(how = BySeleniumId.class, using = "TrackedRacesListComposite")
private WebElement trackedRacesListComposite;
@FindBy(how = BySeleniumId.class, using = "AddConnectionButton")
private WebElement addConnectionButton;
@FindBy(how = BySeleniumId.class, using = "TracTracConfigurationWithSecurityDTOTable")
private WebElement configurationTable;
/**
* <p></p>
*
@@ -123,40 +119,34 @@ public class TracTracEventManagementPanelPO extends PageArea {
super(driver, element);
}
/**
* <p>Lists all available trackable races for the given URL. The list of the races can be obtained via
* {@link #getTrackableRaces()}.</p>
*
* @param url
* The URL for which the races are to list.
*/
public void listTrackableRaces(String url) {
listTrackableRaces("", "", url); //$NON-NLS-1$//$NON-NLS-2$
public void addConnectionAndListTrackableRaces(String url) {
AddTracTracConnectionDialogPO dialog = addConnection();
dialog.setJsonUrl(url);
dialog.pressOk();
listRacesForExistingConnection(url);
}
/**
* <p>Lists all available trackable races for the given URL. The list of the races can be obtained via
* {@link #getTrackableRaces()}.</p>
*
* @param url
* The URL for which the races are to list.
*/
public void listTrackableRaces(String liveURI, String storedURI, String jsonURL) {
this.liveURITextBox.clear();
this.liveURITextBox.sendKeys(liveURI);
this.storedURITextBox.clear();
this.storedURITextBox.sendKeys(storedURI);
this.jsonURLTextBox.clear();
this.jsonURLTextBox.sendKeys(jsonURL);
public void listRacesForExistingConnection(String url) {
CellTablePO<DataEntryPO> tablePO = getConfigurationTable();
tablePO.waitForTableToShowData();
tablePO.selectEntries(entry -> {
return url.equals(entry.getColumnContent("JSON URL"));
}, () -> false);
this.listRacesButton.click();
waitForAjaxRequests();
}
public AddTracTracConnectionDialogPO addConnection() {
addConnectionButton.click();
final WebElement dialog = findElementBySeleniumId(this.driver, "EditTracTracConnectionDialog");
return new AddTracTracConnectionDialogPO(this.driver, dialog);
}
private CellTablePO<DataEntryPO> getConfigurationTable() {
return new GenericCellTablePO<>(this.driver, this.configurationTable, DataEntryPO.class);
}
/**
* <p>Returns the list of all available trackable races. This list will be empty if no race is available or if no
* race was specified before.</p>
@@ -5,6 +5,8 @@ import org.junit.Test;
import static org.hamcrest.core.Is.*;
import static org.junit.Assert.*;
import org.junit.Before;
import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage;
import com.sap.sailing.selenium.pages.adminconsole.tractrac.TracTracEventManagementPanelPO;
import com.sap.sailing.selenium.test.AbstractSeleniumTest;
@@ -19,6 +21,13 @@ public class TestRaceFilterForTracTracEvents extends AbstractSeleniumTest {
private static final String BMW_CUP_JSON_URL =
"http://kml.skitrac.traclive.dk/events/event_20120803_BMWCup/jsonservice.php"; //$NON-NLS-1$
@Override
@Before
public void setUp() {
clearState(getContextRoot());
super.setUp();
}
/**
* <p>Test with no filter at all, which means we expect all TracTrac races are displayed for a given event.</p>
*/
@@ -26,7 +35,7 @@ public class TestRaceFilterForTracTracEvents extends AbstractSeleniumTest {
public void testNoFilter() {
AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot());
TracTracEventManagementPanelPO tracTracEvents = adminConsole.goToTracTracEvents();
tracTracEvents.listTrackableRaces(BMW_CUP_JSON_URL);
tracTracEvents.addConnectionAndListTrackableRaces(BMW_CUP_JSON_URL);
assertThat(tracTracEvents.getTrackableRaces().size(), is(12));
}
@@ -37,7 +46,7 @@ public class TestRaceFilterForTracTracEvents extends AbstractSeleniumTest {
public void testPartialFilter() {
AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot());
TracTracEventManagementPanelPO tracTracEvents = adminConsole.goToTracTracEvents();
tracTracEvents.listTrackableRaces(BMW_CUP_JSON_URL);
tracTracEvents.addConnectionAndListTrackableRaces(BMW_CUP_JSON_URL);
tracTracEvents.setFilterForTrackableRaces("BMW Cup Race 1"); //$NON-NLS-1$
assertThat(tracTracEvents.getTrackableRaces().size(), is(5));
}
@@ -49,7 +58,7 @@ public class TestRaceFilterForTracTracEvents extends AbstractSeleniumTest {
public void testExactFilter() {
AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot());
TracTracEventManagementPanelPO tracTracEvents = adminConsole.goToTracTracEvents();
tracTracEvents.listTrackableRaces(BMW_CUP_JSON_URL);
tracTracEvents.addConnectionAndListTrackableRaces(BMW_CUP_JSON_URL);
tracTracEvents.setFilterForTrackableRaces("BMW Cup Race 12"); //$NON-NLS-1$
assertThat(tracTracEvents.getTrackableRaces().size(), is(1));
}
@@ -61,7 +70,7 @@ public class TestRaceFilterForTracTracEvents extends AbstractSeleniumTest {
public void testNoneMatchingFilter() {
AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot());
TracTracEventManagementPanelPO tracTracEvents = adminConsole.goToTracTracEvents();
tracTracEvents.listTrackableRaces(BMW_CUP_JSON_URL);
tracTracEvents.addConnectionAndListTrackableRaces(BMW_CUP_JSON_URL);
tracTracEvents.setFilterForTrackableRaces("BMW Cup Race 18"); //$NON-NLS-1$
assertThat(tracTracEvents.getTrackableRaces().size(), is(0));
}
@@ -259,7 +259,7 @@ public class TestRefreshableSelectionModel extends AbstractSeleniumTest {
// Start the tracking for the races and wait until they are ready to use
TracTracEventManagementPanelPO tracTracEvents = adminConsole.goToTracTracEvents();
tracTracEvents.listTrackableRaces(IDM_5O5_2013_JSON_URL);
tracTracEvents.addConnectionAndListTrackableRaces(IDM_5O5_2013_JSON_URL);
tracTracEvents.setReggataForTracking(this.regatta);
tracTracEvents.setTrackSettings(false, false, false);
tracTracEvents.startTrackingForRaces(this.trackableRaces);
@@ -129,7 +129,7 @@ public class TestSmartphoneTrackingEventManagementPanel extends AbstractSelenium
// Start the tracking for the races and wait until they are ready to use
TracTracEventManagementPanelPO tracTracEvents = adminConsole.goToTracTracEvents();
tracTracEvents.listTrackableRaces(IDM_5O5_2013_JSON_URL);
tracTracEvents.addConnectionAndListTrackableRaces(IDM_5O5_2013_JSON_URL);
tracTracEvents.setReggataForTracking(this.regatta);
tracTracEvents.setTrackSettings(false, false, false);
tracTracEvents.startTrackingForRaces(this.trackableRaces);
@@ -63,7 +63,7 @@ public class TestStartAndStopTrackingForTracTracEvents extends AbstractSeleniumT
RegattaStructureManagementPanelPO regattaStructure = adminConsole.goToRegattaStructure();
regattaStructure.createRegatta(bmwCupDescriptor);
TracTracEventManagementPanelPO tracTracEvents = adminConsole.goToTracTracEvents();
tracTracEvents.listTrackableRaces(BMW_CUP_JSON_URL);
tracTracEvents.addConnectionAndListTrackableRaces(BMW_CUP_JSON_URL);
tracTracEvents.setReggataForTracking(bmwCupDescriptor);
tracTracEvents.setTrackSettings(false, false, false);
tracTracEvents.startTrackingForRace(this.trackableRace);
@@ -81,7 +81,7 @@ public class TestStartAndStopTrackingForTracTracEvents extends AbstractSeleniumT
RegattaStructureManagementPanelPO regattaStructure = adminConsole.goToRegattaStructure();
regattaStructure.createRegatta(new RegattaDescriptor(BMW_CUP_EVENT, BMW_CUP_BOAT_CLASS));
TracTracEventManagementPanelPO tracTracEvents = adminConsole.goToTracTracEvents();
tracTracEvents.listTrackableRaces(BMW_CUP_JSON_URL);
tracTracEvents.addConnectionAndListTrackableRaces(BMW_CUP_JSON_URL);
tracTracEvents.setReggataForTracking(DEFAULT_REGATTA);
tracTracEvents.setTrackSettings(false, false, false);
tracTracEvents.startTrackingForRacesAndAcceptDefaultRegattaWarning(this.trackableRace);
@@ -96,7 +96,7 @@ public class TestStartAndStopTrackingForTracTracEvents extends AbstractSeleniumT
regattaStructure.createRegatta(bmwCupDescriptor);
regattaStructure.createRegatta(idm2013Descriptor);
TracTracEventManagementPanelPO tracTracEvents = adminConsole.goToTracTracEvents();
tracTracEvents.listTrackableRaces(BMW_CUP_JSON_URL);
tracTracEvents.addConnectionAndListTrackableRaces(BMW_CUP_JSON_URL);
tracTracEvents.setReggataForTracking(idm2013Descriptor);
tracTracEvents.setTrackSettings(false, false, false);
tracTracEvents.startTrackingForRaceAndAwaitBoatClassError(this.trackableRace, IDM_2013_BOAT_CLASS);
@@ -63,7 +63,7 @@ public class TestAutoPlay extends AbstractSeleniumTest {
String.format(BMW_RACE, 1));
TracTracEventManagementPanelPO tracTracEvents = adminConsole.goToTracTracEvents();
tracTracEvents.listTrackableRaces(BMW_CUP_JSON_URL);
tracTracEvents.addConnectionAndListTrackableRaces(BMW_CUP_JSON_URL);
RegattaDescriptor bmwCupDescriptor = new RegattaDescriptor(BMW_CUP_EVENT, BMW_CUP_BOAT_CLASS);
tracTracEvents.setReggataForTracking(bmwCupDescriptor);
tracTracEvents.setTrackSettings(true, false, false);
@@ -9,6 +9,7 @@ import static org.junit.Assert.assertTrue;
import java.util.List;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage;
@@ -57,6 +58,7 @@ public class TestColumnsInLeaderboard extends AbstractSeleniumTest {
configureLeaderboard();
}
@Ignore
@Test
public void testCorrectDisplayOfAllColumns() {
LeaderboardPage leaderboard = LeaderboardPage.goToPage(getWebDriver(), getContextRoot(), LEADERBOARD, true);
@@ -106,7 +108,7 @@ public class TestColumnsInLeaderboard extends AbstractSeleniumTest {
assertFalse(races.iterator().next().isLinked()); // removing the race must make the race column unlinked.
}
// now back to the TracTrac management panel, load the race again and make sure it is auto-linked to the column
startTrackingRaceAndStopWhenFinished(adminConsole);
startTrackingRaceAndStopWhenFinished(adminConsole, false);
{
LeaderboardConfigurationPanelPO leaderboardConfiguration = adminConsole.goToLeaderboardConfiguration();
LeaderboardDetailsPanelPO details = leaderboardConfiguration.getLeaderboardDetails(LEADERBOARD);
@@ -128,7 +130,7 @@ public class TestColumnsInLeaderboard extends AbstractSeleniumTest {
seriesDialog.pressOk();
regattaDetails.deleteSeries("Default");
// Start the tracking for the races and wait until they are ready to use
startTrackingRaceAndStopWhenFinished(adminConsole);
startTrackingRaceAndStopWhenFinished(adminConsole, true);
LeaderboardConfigurationPanelPO leaderboardConfiguration = adminConsole.goToLeaderboardConfiguration();
leaderboardConfiguration.createRegattaLeaderboard(this.regatta);
LeaderboardDetailsPanelPO leaderboardDetails = leaderboardConfiguration.getLeaderboardDetails(LEADERBOARD);
@@ -138,9 +140,13 @@ public class TestColumnsInLeaderboard extends AbstractSeleniumTest {
/**
* @param adminConsole
*/
private void startTrackingRaceAndStopWhenFinished(AdminConsolePage adminConsole) {
private void startTrackingRaceAndStopWhenFinished(AdminConsolePage adminConsole, boolean addConnection) {
TracTracEventManagementPanelPO tracTracEvents = adminConsole.goToTracTracEvents();
tracTracEvents.listTrackableRaces(KIELER_WOCHE_2013_JSON_URL);
if (addConnection) {
tracTracEvents.addConnectionAndListTrackableRaces(KIELER_WOCHE_2013_JSON_URL);
} else {
tracTracEvents.listRacesForExistingConnection(KIELER_WOCHE_2013_JSON_URL);
}
tracTracEvents.setReggataForTracking(this.regatta);
tracTracEvents.setTrackSettings(false, false, false);
tracTracEvents.startTrackingForRace(this.trackableRace);
@@ -204,7 +204,7 @@ public class TestLeaderboardConfiguration extends AbstractSeleniumTest {
// Start the tracking for the races and wait until they are ready to use
TracTracEventManagementPanelPO tracTracEvents = adminConsole.goToTracTracEvents();
tracTracEvents.listTrackableRaces(IDM_5O5_2013_JSON_URL);
tracTracEvents.addConnectionAndListTrackableRaces(IDM_5O5_2013_JSON_URL);
tracTracEvents.setReggataForTracking(this.regatta);
tracTracEvents.setTrackSettings(false, false, false);
tracTracEvents.startTrackingForRaces(trackableRaces);
@@ -104,7 +104,7 @@ public class TestSwitchingOffStartOfRaceInference extends AbstractSeleniumTest {
regattaDetails.deleteSeries("Default");
// Start the tracking for the races and wait until they are ready to use
TracTracEventManagementPanelPO tracTracEvents = adminConsole.goToTracTracEvents();
tracTracEvents.listTrackableRaces(BMW_CUP_JSON_URL);
tracTracEvents.addConnectionAndListTrackableRaces(BMW_CUP_JSON_URL);
tracTracEvents.setReggataForTracking(this.regatta);
tracTracEvents.setTrackSettings(false, false, false);
tracTracEvents.startTrackingForRace(this.trackableRace);
@@ -66,7 +66,7 @@ public class TestLeaderboardEditing extends AbstractSeleniumTest {
private void startTrackingRaceAndWait(AdminConsolePage adminConsole, String jsonUrl, TrackableRaceDescriptor trackableRace,
TrackedRaceDescriptor trackedRace, long waitingTime) {
TracTracEventManagementPanelPO tracTracEvents = adminConsole.goToTracTracEvents();
tracTracEvents.listTrackableRaces(jsonUrl);
tracTracEvents.addConnectionAndListTrackableRaces(jsonUrl);
tracTracEvents.setTrackSettings(false, true, false);
tracTracEvents.startTrackingForRace(trackableRace);
TrackedRacesListPO trackedRacesList = tracTracEvents.getTrackedRacesList();
@@ -340,7 +340,7 @@ public class SettingsTest extends AbstractSeleniumTest {
TrackedRaceDescriptor trackedRace = new TrackedRaceDescriptor(BMW_CUP_REGATTA, BMW_CUP_BOAT_CLASS,
String.format(BMW_RACE, 1));
TracTracEventManagementPanelPO tracTracEvents = adminConsole.goToTracTracEvents();
tracTracEvents.listTrackableRaces(BMW_CUP_JSON_URL);
tracTracEvents.addConnectionAndListTrackableRaces(BMW_CUP_JSON_URL);
RegattaDescriptor bmwCupDescriptor = new RegattaDescriptor(BMW_CUP_EVENT, BMW_CUP_BOAT_CLASS);
tracTracEvents.setReggataForTracking(bmwCupDescriptor);
tracTracEvents.setTrackSettings(true, false, false);
@@ -124,7 +124,7 @@ public class SimulatorOverlayTest extends AbstractSeleniumTest {
}
private void trackRacesFor49er(final RegattaDescriptor regattaDescriptor, final TracTracEventManagementPanelPO tracTracEvents) {
tracTracEvents.listTrackableRaces(JSON_URL);
tracTracEvents.addConnectionAndListTrackableRaces(JSON_URL);
tracTracEvents.setReggataForTracking(regattaDescriptor);
tracTracEvents.setTrackSettings(true, false, false);
tracTracEvents.setFilterForTrackableRaces("(49er)");
Loaded 100 of 132 files, more files were not shown because too many files have changed in this diff. Show more