Merge branch 'master' into bug2447

This commit is contained in:
Steffen Schaefer
2016-12-09 09:56:39 +01:00
79 changed files with 68445 additions and 723 deletions
@@ -32,7 +32,6 @@
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.dashboards.gwt&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-servlet.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
@@ -31,7 +31,6 @@
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.dashboards.gwt&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-servlet.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
@@ -7,7 +7,7 @@
<listEntry value="1"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.sap.sailing.declination.impl.DeclinationStore"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="2016 2016 1"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="2017 2017 1"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.declination"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dhttp.proxyHost=proxy.wdf.sap.corp -Dhttp.proxyPort=8080"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dhttp.proxyHost=proxy.wdf.sap.corp -Dhttp.proxyPort=8080 -Dhttps.proxyHost=proxy.wdf.sap.corp -Dhttps.proxyPort=8080"/>
</launchConfiguration>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,46 @@
package com.sap.sailing.domain.common.dto;
import java.util.Objects;
public class CompetitorWithToolTipDTO {
private CompetitorDTO competitor;
private String toolTipMessage;
public CompetitorWithToolTipDTO() {
}
public CompetitorWithToolTipDTO(CompetitorDTO competitor, String toolTipMessage) {
this.competitor = competitor;
this.toolTipMessage = toolTipMessage;
}
public CompetitorDTO getCompetitor() {
return competitor;
}
public String getToolTipMessage() {
return toolTipMessage;
}
@Override
public int hashCode() {
return Objects.hash(competitor, toolTipMessage);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
CompetitorWithToolTipDTO other = (CompetitorWithToolTipDTO) obj;
return Objects.equals(competitor, other.getCompetitor())
&& Objects.equals(toolTipMessage, other.getToolTipMessage());
}
}
@@ -5,8 +5,8 @@ iOSUsers=Start iOS App
androidUsers=Start Android App
alternativelyVisitThisLink=If you are reading this mail on the device with the App already installed, simply click below to start:
buoyTender=buoy tender
appStoreInstallText=If don't have the App yet, please install:
appStoreInstallText=If you don't have the App yet, please install:
appIos=Install iOS App
appAndroid=Install Android App
buoyPingerAppName=SAP Sailing Buoy Pinger
sailInSightAppName=SAP Sail InSight
sailInSightAppName=SAP Sail InSight
@@ -8,4 +8,8 @@ public interface LeaderboardBase extends Named {
* this method returns it; otherwise, <code>null</code> is returned.
*/
String getDisplayName();
void addLeaderboardChangeListener(LeaderboardChangeListener listener);
void removeLeaderboardChangeListener(LeaderboardChangeListener listener);
}
@@ -0,0 +1,6 @@
package com.sap.sailing.domain.base;
public interface LeaderboardChangeListener {
void nameChanged(String oldName, String newName);
void displayNameChanged(String oldDisplayName, String newDisplayName);
}
@@ -140,5 +140,4 @@ public class BoatClassImpl extends NamedImpl implements BoatClass {
public BoatHullType getHullType() {
return hullType;
}
}
@@ -0,0 +1,64 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import org.junit.Test;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.impl.CourseAreaImpl;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class LeaderboardDTOCacheInvalidationTest {
@Test
public void testFlexibleLeaderboardNameChangeInvalidatesCache()
throws NoWindException, InterruptedException, ExecutionException {
final String oldName = "My Flexible Leaderboard";
final String newName = "My New Leaderboard";
FlexibleLeaderboard l = new FlexibleLeaderboardImpl(oldName,
new ThresholdBasedResultDiscardingRuleImpl(new int[0]), new LowPoint(),
new CourseAreaImpl("My Course Area", UUID.randomUUID()));
final TimePoint now = MillisecondsTimePoint.now();
LeaderboardDTO dto = l.getLeaderboardDTO(now, Collections.<String> emptySet(),
/* addOverallDetails */ false, /* trackedRegattaRegistry */ null, DomainFactory.INSTANCE,
/* fillTotalPointsUncorrected */ false);
assertEquals(oldName, dto.name);
l.setName(newName);
LeaderboardDTO dtoNew = l.getLeaderboardDTO(now, Collections.<String> emptySet(),
/* addOverallDetails */ false, /* trackedRegattaRegistry */ null, DomainFactory.INSTANCE,
/* fillTotalPointsUncorrected */ false);
assertEquals(newName, dtoNew.name);
}
@Test
public void testFlexibleLeaderboardDisplayNameChangeInvalidatesCache()
throws NoWindException, InterruptedException, ExecutionException {
final String name = "My Flexible Leaderboard";
final String oldDisplayName = "D1";
final String newDisplayName = "D2";
FlexibleLeaderboard l = new FlexibleLeaderboardImpl(name,
new ThresholdBasedResultDiscardingRuleImpl(new int[0]), new LowPoint(),
new CourseAreaImpl("My Course Area", UUID.randomUUID()));
l.setDisplayName(oldDisplayName);
final TimePoint now = MillisecondsTimePoint.now();
LeaderboardDTO dto = l.getLeaderboardDTO(now, Collections.<String> emptySet(),
/* addOverallDetails */ false, /* trackedRegattaRegistry */ null, DomainFactory.INSTANCE,
/* fillTotalPointsUncorrected */ false);
assertEquals(oldDisplayName, dto.getDisplayName());
l.setDisplayName(newDisplayName);
LeaderboardDTO dtoNew = l.getLeaderboardDTO(now, Collections.<String> emptySet(),
/* addOverallDetails */ false, /* trackedRegattaRegistry */ null, DomainFactory.INSTANCE,
/* fillTotalPointsUncorrected */ false);
assertEquals(newDisplayName, dtoNew.getDisplayName());
}
}
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.base.impl;
import com.sap.sailing.domain.base.LeaderboardBase;
import com.sap.sailing.domain.base.LeaderboardChangeListener;
public class LeaderboardBaseImpl implements LeaderboardBase {
private static final long serialVersionUID = -5332972141344617372L;
@@ -23,4 +24,11 @@ public class LeaderboardBaseImpl implements LeaderboardBase {
return displayName;
}
@Override
public void addLeaderboardChangeListener(LeaderboardChangeListener listener) {
}
@Override
public void removeLeaderboardChangeListener(LeaderboardChangeListener listener) {
}
}
@@ -12,6 +12,7 @@ import java.util.concurrent.ConcurrentMap;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CompetitorChangeListener;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.LeaderboardChangeListener;
import com.sap.sailing.domain.base.Nationality;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnListener;
@@ -40,11 +41,30 @@ import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
*/
public class LeaderboardCacheManager {
private final WeakHashMap<Leaderboard, CacheInvalidationUponScoreCorrectionListener> scoreCorrectionListeners;
private final NamedReentrantReadWriteLock scoreCorrectionAndCompetitorChangeListenersLock;
private final WeakHashMap<Leaderboard, NameChangeListener> nameChangeListeners;
private final NamedReentrantReadWriteLock scoreCorrectionAndCompetitorAndNameChangeListenersLock;
private final WeakHashMap<Leaderboard, CacheInvalidationUponCompetitorChangeListener> competitorChangeListeners;
private final ConcurrentWeakHashMap<Leaderboard, ConcurrentMap<TrackedRace, Set<CacheInvalidationListener>>> invalidationListenersPerLeaderboard;
private final WeakHashMap<Leaderboard, RaceColumnListener> raceColumnListeners;
private class NameChangeListener implements LeaderboardChangeListener {
private final Leaderboard leaderboard;
public NameChangeListener(Leaderboard leaderboard) {
this.leaderboard = leaderboard;
}
@Override
public void nameChanged(String oldName, String newName) {
removeFromCache(leaderboard);
}
@Override
public void displayNameChanged(String oldDisplayName, String newDisplayName) {
removeFromCache(leaderboard);
}
}
private class CacheInvalidationListener extends AbstractRaceChangeListener {
private final Leaderboard leaderboard;
private final TrackedRace trackedRace;
@@ -194,8 +214,9 @@ public class LeaderboardCacheManager {
this.invalidationListenersPerLeaderboard = new ConcurrentWeakHashMap<>();
this.raceColumnListeners = new WeakHashMap<Leaderboard, RaceColumnListener>();
this.scoreCorrectionListeners = new WeakHashMap<>();
this.scoreCorrectionAndCompetitorChangeListenersLock = new NamedReentrantReadWriteLock(
"Score correction and competitor change listeners", /* fair */false);
this.nameChangeListeners = new WeakHashMap<>();
this.scoreCorrectionAndCompetitorAndNameChangeListenersLock = new NamedReentrantReadWriteLock(
"Score correction and competitor and name change listeners", /* fair */false);
this.competitorChangeListeners = new WeakHashMap<>();
}
@@ -213,14 +234,16 @@ public class LeaderboardCacheManager {
synchronized (raceColumnListeners) {
leaderboard.removeRaceColumnListener(raceColumnListeners.remove(leaderboard));
}
LockUtil.lockForWrite(scoreCorrectionAndCompetitorChangeListenersLock);
LockUtil.lockForWrite(scoreCorrectionAndCompetitorAndNameChangeListenersLock);
final CacheInvalidationUponScoreCorrectionListener removedScoreCorrectionListener;
final CacheInvalidationUponCompetitorChangeListener removedCompetitorChangeListener;
final NameChangeListener removedNameChangeListener;
try {
removedScoreCorrectionListener = scoreCorrectionListeners.remove(leaderboard);
removedCompetitorChangeListener = competitorChangeListeners.remove(leaderboard);
removedNameChangeListener = nameChangeListeners.remove(leaderboard);
} finally {
LockUtil.unlockAfterWrite(scoreCorrectionAndCompetitorChangeListenersLock);
LockUtil.unlockAfterWrite(scoreCorrectionAndCompetitorAndNameChangeListenersLock);
}
if (removedScoreCorrectionListener != null) {
leaderboard.getScoreCorrection().removeScoreCorrectionListener(removedScoreCorrectionListener);
@@ -228,6 +251,9 @@ public class LeaderboardCacheManager {
if (removedCompetitorChangeListener != null) {
removedCompetitorChangeListener.removeFromAllCompetitors();
}
if (removedNameChangeListener != null) {
leaderboard.removeLeaderboardChangeListener(removedNameChangeListener);
}
// invalidate after the listeners have been removed; this is important because invalidation may trigger a
// re-calculation which then in turn may call add(leaderboard) asynchronously again which may be executed
// before the listener removal happens here. This could lead to a race condition where listeners are
@@ -248,14 +274,14 @@ public class LeaderboardCacheManager {
private void registerAsListener(final Leaderboard leaderboard) {
// only add as listener again if not yet added
final boolean containsKey;
LockUtil.lockForRead(scoreCorrectionAndCompetitorChangeListenersLock);
LockUtil.lockForRead(scoreCorrectionAndCompetitorAndNameChangeListenersLock);
try {
containsKey = scoreCorrectionListeners.containsKey(leaderboard);
} finally {
LockUtil.unlockAfterRead(scoreCorrectionAndCompetitorChangeListenersLock);
LockUtil.unlockAfterRead(scoreCorrectionAndCompetitorAndNameChangeListenersLock);
}
if (!containsKey) {
LockUtil.lockForWrite(scoreCorrectionAndCompetitorChangeListenersLock);
LockUtil.lockForWrite(scoreCorrectionAndCompetitorAndNameChangeListenersLock);
try {
for (TrackedRace trackedRace : leaderboard.getTrackedRaces()) {
registerListener(leaderboard, trackedRace);
@@ -264,9 +290,12 @@ public class LeaderboardCacheManager {
leaderboard);
final CacheInvalidationUponCompetitorChangeListener competitorChangeListener = new CacheInvalidationUponCompetitorChangeListener(
leaderboard);
final NameChangeListener nameChangeListener = new NameChangeListener(leaderboard);
leaderboard.getScoreCorrection().addScoreCorrectionListener(scoreCorrectionListener);
leaderboard.addLeaderboardChangeListener(nameChangeListener);
scoreCorrectionListeners.put(leaderboard, scoreCorrectionListener);
competitorChangeListeners.put(leaderboard, competitorChangeListener);
nameChangeListeners.put(leaderboard, nameChangeListener);
final RaceColumnListener raceColumnListener = new RaceColumnListenerWithDefaultAction() {
private static final long serialVersionUID = 8165124797028386317L;
@@ -294,7 +323,7 @@ public class LeaderboardCacheManager {
raceColumnListeners.put(leaderboard, raceColumnListener);
}
} finally {
LockUtil.unlockAfterWrite(scoreCorrectionAndCompetitorChangeListenersLock);
LockUtil.unlockAfterWrite(scoreCorrectionAndCompetitorAndNameChangeListenersLock);
}
}
}
@@ -22,6 +22,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RunnableFuture;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@@ -35,6 +36,7 @@ import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.LeaderboardChangeListener;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnInSeries;
@@ -150,6 +152,8 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
*/
private transient Set<CacheInvalidationListener> cacheInvalidationListeners;
private transient Set<LeaderboardChangeListener> leaderboardChangeListeners;
private static final ExecutorService executor = ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor();
private transient LiveLeaderboardUpdater liveLeaderboardUpdater;
@@ -331,7 +335,8 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
private void initTransientFields() {
this.raceDetailsAtEndOfTrackingCache = new HashMap<>();
this.cacheInvalidationListeners = new HashSet<CacheInvalidationListener>();
this.cacheInvalidationListeners = new HashSet<>();
this.leaderboardChangeListeners = new HashSet<>();
// When many updates are triggered in a short period of time by a single thread, ensure that the single thread
// providing the updates is not outperformed by all the re-calculations happening here. Leave at least one
// core to other things, but by using at least three threads ensure that no simplistic deadlocks may occur.
@@ -361,7 +366,27 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
@Override
public void setDisplayName(String displayName) {
final String oldDisplayName = this.displayName;
this.displayName = displayName;
notifyLeaderboardChangeListeners(listener->{
try {
listener.displayNameChanged(oldDisplayName, displayName);
} catch (Exception e) {
logger.log(Level.WARNING, "Exception trying to notify listener "+listener+" about the display name of leaderboard "+
getName()+" changing from "+oldDisplayName+" to "+displayName, e);
}
});
}
protected void notifyLeaderboardChangeListeners(Consumer<LeaderboardChangeListener> notifier) {
for (final LeaderboardChangeListener listener : leaderboardChangeListeners) {
try {
notifier.accept(listener);
} catch (Exception e) {
logger.log(Level.WARNING, "Exception trying to notify listener "+listener+" about a change in leaderboard "+
getName(), e);
}
}
}
@Override
@@ -663,6 +688,16 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
getRaceColumnListeners().removeRaceColumnListener(listener);
}
@Override
public void addLeaderboardChangeListener(LeaderboardChangeListener listener) {
leaderboardChangeListeners.add(listener);
}
@Override
public void removeLeaderboardChangeListener(LeaderboardChangeListener listener) {
leaderboardChangeListeners.remove(listener);
}
@Override
public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint) throws NoWindException {
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
@@ -1219,6 +1254,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
result.type = getLeaderboardType();
result.competitors = new ArrayList<CompetitorDTO>();
result.name = this.getName();
result.displayName = this.getDisplayName();
result.competitorDisplayNames = new HashMap<CompetitorDTO, String>();
for (Competitor suppressedCompetitor : this.getSuppressedCompetitors()) {
result.setSuppressed(baseDomainFactory.convertToCompetitorDTO(suppressedCompetitor), true);
@@ -10,6 +10,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
@@ -140,7 +141,16 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
if (newName == null) {
throw new IllegalArgumentException("A leaderboard's name must not be null");
}
final String oldName = this.name;
this.name = newName;
notifyLeaderboardChangeListeners(listener->{
try {
listener.nameChanged(oldName, newName);
} catch (Exception e) {
logger.log(Level.WARNING, "Exception trying to notify listener "+listener+" about the name of leaderboard "+
getName()+" changing from "+oldName+" to "+newName, e);
}
});
}
@Override
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.polars;
import java.util.Set;
import java.util.function.Consumer;
import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
@@ -162,22 +163,21 @@ public interface PolarDataService {
void insertExistingFixes(TrackedRace trackedRace);
/**
*
* @param boatClass When polars of this boat class change, the listener will be notified. May not be null.
* @param listener may not be null
*/
void registerListener(BoatClass boatClass, PolarsChangedListener listener);
/**
*
* @param boatClass
* @param listener
*/
void unregisterListener(BoatClass boatClass, PolarsChangedListener listener);
void registerDomainFactory(DomainFactory domainFactory);
void unregisterDomainFactory(DomainFactory domainFactory);
/**
* When called, the method blocks until a {@link DomainFactory} has been {@link #registerDomainFactory(DomainFactory) registered}
* with this service, then lets the {@code consumer} accept that domain factory.
* @throws InterruptedException
*/
void runWithDomainFactory(Consumer<DomainFactory> consumer) throws InterruptedException;
}
+13 -13
View File
@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src/main/resources"/>
<classpathentry excluding="com/sap/sailing/gwt/ui/jre/" kind="src" path="src/main/java"/>
<classpathentry exported="true" kind="lib" path="WEB-INF/lib/gwt-maps-api-3.9.0-build-17.jar"/>
<classpathentry exported="true" kind="lib" path="WEB-INF/lib/gwt-ajaxloader.jar"/>
<classpathentry exported="true" kind="lib" path="WEB-INF/lib/gwt-bootstrap-2.3.2.0.jar"/>
<classpathentry exported="true" kind="lib" path="WEB-INF/classes/"/>
<classpathentry kind="con" path="com.google.gwt.eclipse.core.GWT_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="output" path="bin"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src/main/resources"/>
<classpathentry excluding="com/sap/sailing/gwt/ui/jre/" kind="src" path="src/main/java"/>
<classpathentry exported="true" kind="lib" path="WEB-INF/lib/gwt-maps-api-3.9.0-build-17.jar"/>
<classpathentry exported="true" kind="lib" path="WEB-INF/lib/gwt-ajaxloader.jar"/>
<classpathentry exported="true" kind="lib" path="WEB-INF/lib/gwt-bootstrap-2.3.2.0.jar"/>
<classpathentry exported="true" kind="lib" path="WEB-INF/classes/"/>
<classpathentry kind="con" path="com.google.gwt.eclipse.core.GWT_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="output" path="bin"/>
</classpath>
@@ -1,12 +1,12 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.7
org.eclipse.jdt.core.compiler.source=1.8
@@ -4,7 +4,7 @@ Bundle-Name: Sailing App GWT UI
Bundle-SymbolicName: com.sap.sailing.gwt.ui
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Require-Bundle: com.sap.sailing.domain,
com.sap.sailing.domain.shared.android,
com.sap.sailing.server,
@@ -201,7 +201,7 @@ public class LeaderboardWithHeaderPerspective extends AbstractPerspectiveComposi
leaderboardName, errorReporter, stringMessages,
userAgent, /*showRaceDetails */false, /* competitorSearchTextBox */ null, /* showRegattaRank */
/* showSelectionCheckbox */false, /* raceTimesInfoProvider */null, false, /* autoExpandLastRaceColumn */
/* adjustTimerDelay */true, /*autoApplyTopNFilter*/ false, false);
/* adjustTimerDelay */ true, /* autoApplyTopNFilter */ false, /* showCompetitorFilterStatus */ false, /* enableSyncScroller */ false);
return leaderboardPanel;
}
@@ -26,13 +26,9 @@
<div class="{res.mediaCss.grid}">
<div class="{res.mediaCss.small12} {res.mediaCss.columns}">
<div class="{local_res.css.letItScroll}">
<table class="{local_res.css.regattaleaderboard_table}">
<tr>
<td>
<g:HTMLPanel ui:field="oldLeaderboardPanel"></g:HTMLPanel>
</td>
</tr>
</table>
<div class="{local_res.css.regattaleaderboard_table}">
<g:HTMLPanel ui:field="oldLeaderboardPanel"></g:HTMLPanel>
</div>
</div>
</div>
</div>
@@ -51,8 +51,8 @@ public class RegattaAnalyticsDataManager {
if (leaderboardPanel == null) {
leaderboardPanel = new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderboardSettings, true, preselectedRace,
competitorSelectionProvider, timer, leaderboardGroupName, leaderboardName, errorReporter,
StringMessages.INSTANCE, userAgent, showRaceDetails, /* competitorSearchTextBox */ null, /* showSelectionCheckbox */ true, /* raceTimesInfoProvider */null, autoExpandLastRaceColumn, /* adjustTimerDelay */
true, false, false);
StringMessages.INSTANCE, userAgent, showRaceDetails, /* competitorSearchTextBox */ null, /* showSelectionCheckbox */ true, /* raceTimesInfoProvider */ null, autoExpandLastRaceColumn,
/* adjustTimerDelay */ true, /* autoApplyTopNFilter */ false, /* showCompetitorFilterStatus */ false, /* enableSyncScroller */ true);
}
return leaderboardPanel;
}
@@ -65,7 +65,8 @@ public class EventSeriesAnalyticsDataManager {
overallLeaderboardPanel = new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderboardSettings, true, preselectedRace,
competitorSelectionProvider, timer, leaderboardGroupName, leaderboardName, errorReporter,
StringMessages.INSTANCE, userAgent, showRaceDetails, /* competitorSearchTextBox */ null, /* showSelectionCheckbox */ true,
/* raceTimesInfoProvider */null, autoExpandLastRaceColumn, /* adjustTimerDelay */ true, false, false);
/* raceTimesInfoProvider */null, autoExpandLastRaceColumn, /* adjustTimerDelay */ true, /* autoApplyTopNFilter */ false,
/* showCompetitorFilterStatus */ false, /* enableSyncScroller */ false);
}
return overallLeaderboardPanel;
}
@@ -70,7 +70,7 @@ public class PasswordResetActivity extends AbstractActivity implements PasswordR
}
private class ResetPasswordFormValues {
private final NewAccountValidator validator = new NewAccountValidator(i18n_sec);
private final NewAccountValidator validator = new ResetPasswordAccountValidator(i18n_sec);
private final String username, resetSecret;
private String password, passwordConfirmation;
@@ -0,0 +1,20 @@
package com.sap.sailing.gwt.home.shared.places.user.passwordreset;
import com.sap.sse.security.ui.client.component.NewAccountValidator;
import com.sap.sse.security.ui.client.i18n.StringMessages;
public class ResetPasswordAccountValidator extends NewAccountValidator {
public ResetPasswordAccountValidator(StringMessages stringMessages) {
super(stringMessages);
}
@Override
protected String validateUsername(String username) {
// the userName is invalid, if any ErrorMessage is returned
boolean invalidUserName = super.validateUsername(username) != null;
// however since the username is extracted from the reseturl, this means the reseturl is corrupt
return invalidUserName ? stringMessages.cannotResetInvalidURL() : null;
}
}
@@ -146,8 +146,9 @@ public abstract class AbstractCompetitorRegistrationsDialog extends DataEntryDia
private void moveSelected(CompetitorTableWrapper<RefreshableMultiSelectionModel<CompetitorDTO>> from,
CompetitorTableWrapper<RefreshableMultiSelectionModel<CompetitorDTO>> to) {
move(from, to, from.getSelectionModel().getSelectedSet());
validate();
}
protected boolean showOnlyCompetitorsOfLog(){
return showOnlyCompetitorsOfLogCheckBox.getValue();
}
@@ -149,3 +149,8 @@
.cellTableTotalColumn {
background-color: #e0e0e0 !important;
}
.cellTableDisabledRow {
background: #e5e5e5;
opacity : 0.8;
}
@@ -54,6 +54,10 @@ public interface AdminConsoleTableResources extends CellTable.Resources {
*/
String cellTableCheckboxDeselected();
/**
* Applied to the disabled rows
*/
String cellTableDisabledRow();
}
@Override
@@ -18,7 +18,9 @@ import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.ImageResourceRenderer;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.view.client.CellPreviewEvent;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.CompetitorWithToolTipDTO;
import com.sap.sailing.gwt.ui.adminconsole.ColorColumn.ColorRetriever;
import com.sap.sailing.gwt.ui.client.FlagImageResolver;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
@@ -378,4 +380,25 @@ public class CompetitorTableWrapper<S extends RefreshableSelectionModel<Competit
}
});
}
/**
* This method makes rows grayed out with a tool tip
*/
public void grayOutCompetitors(final List<CompetitorWithToolTipDTO> competitors) {
table.addCellPreviewHandler((CellPreviewEvent<CompetitorDTO> event) -> {
for (CompetitorWithToolTipDTO competitor : competitors) {
if (competitor.getCompetitor().equals(event.getValue())) {
table.getRowElement(event.getIndex()).setTitle(competitor.getToolTipMessage());
}
}
});
table.setRowStyles((CompetitorDTO row, int rowIndex) -> {
for (CompetitorWithToolTipDTO competitor : competitors) {
if (competitor.getCompetitor().equals(row)) {
return tableRes.cellTableStyle().cellTableDisabledRow();
}
}
return "";
});
}
}
@@ -1,7 +1,14 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.google.gwt.event.dom.client.ClickEvent;
@@ -10,6 +17,10 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlowPanel;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.CompetitorWithToolTipDTO;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.gwt.ui.client.ParallelExecutionCallback;
import com.sap.sailing.gwt.ui.client.ParallelExecutionHolder;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.gwt.client.ErrorReporter;
@@ -20,9 +31,11 @@ public class RaceLogCompetitorRegistrationDialog extends AbstractCompetitorRegis
private String fleetName;
private String raceColumnName;
private CheckBox competitorRegistrationInRaceLogCheckBox;
private final Map<String, Set<CompetitorDTO>> fleetNameWithCompetitors;
private static class Validator implements com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator<Set<CompetitorDTO>> {
private CheckBox competitorRegistrationInRaceLogCheckBox;
private Map<String, Set<CompetitorDTO>> fleetsWithCompetitors;
private final StringMessages stringMessages;
public Validator(StringMessages stringMessages) {
@@ -31,15 +44,38 @@ public class RaceLogCompetitorRegistrationDialog extends AbstractCompetitorRegis
@Override
public String getErrorMessage(Set<CompetitorDTO> valueToValidate) {
final String result;
String result = null;
if (getCompetitorRegistrationInRaceLogCheckBox() != null && !getCompetitorRegistrationInRaceLogCheckBox().getValue()) {
result = stringMessages.competitorRegistrationsOnRaceDisabled();
} else {
result = null;
Set<CompetitorDTO> difference = new HashSet<>();
for (Set<CompetitorDTO> competitors : fleetsWithCompetitors.values()) {
difference.addAll(intersection(valueToValidate, competitors));
}
if (!difference.isEmpty()) {
result = stringMessages.warningForDisabledCompetitors(createLineOfCompetitors(difference));
}
}
return result;
}
private String createLineOfCompetitors(Set<CompetitorDTO> competitors) {
StringBuilder lineOfCompetitors = new StringBuilder();
if (!competitors.isEmpty()) {
for (CompetitorDTO competitor : competitors) {
lineOfCompetitors.append(competitor.getName()).append(", ");
}
lineOfCompetitors.delete(lineOfCompetitors.length() - 2, lineOfCompetitors.length() - 1);
}
return lineOfCompetitors.toString();
}
private Set<CompetitorDTO> intersection(Set<CompetitorDTO> firstSet, Set<CompetitorDTO> secondSet) {
Set<CompetitorDTO> result = new LinkedHashSet<>(firstSet);
result.retainAll(secondSet);
return result;
}
public CheckBox getCompetitorRegistrationInRaceLogCheckBox() {
return competitorRegistrationInRaceLogCheckBox;
}
@@ -47,28 +83,89 @@ public class RaceLogCompetitorRegistrationDialog extends AbstractCompetitorRegis
public void setCompetitorRegistrationInRaceLogCheckBox(CheckBox competitorRegistrationInRaceLogCheckBox) {
this.competitorRegistrationInRaceLogCheckBox = competitorRegistrationInRaceLogCheckBox;
}
public void setFleetWithCompetitors(Map<String, Set<CompetitorDTO>> fleetNameWithCompetitors) {
this.fleetsWithCompetitors = fleetNameWithCompetitors;
}
}
public RaceLogCompetitorRegistrationDialog(String boatClass, SailingServiceAsync sailingService,
StringMessages stringMessages, ErrorReporter errorReporter, boolean editable, String leaderboardName,
String raceColumnName, String fleetName,
String raceColumnName, String fleetName, List<FleetDTO> fleets,
com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback<Set<CompetitorDTO>> callback) {
this(sailingService, stringMessages, errorReporter, editable, callback, leaderboardName, boatClass,
raceColumnName, fleetName, new Validator(stringMessages));
raceColumnName, fleetName, fleets, new Validator(stringMessages));
}
public RaceLogCompetitorRegistrationDialog(SailingServiceAsync sailingService, StringMessages stringMessages,
ErrorReporter errorReporter, boolean editable,
com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback<Set<CompetitorDTO>> callback,
String leaderboardName, String boatClass, String raceColumnName, String fleetName, Validator validator) {
String leaderboardName, String boatClass, String raceColumnName, String fleetName, List<FleetDTO> fleets, Validator validator) {
super(sailingService, stringMessages, errorReporter, editable, callback, leaderboardName, boatClass, validator);
this.raceColumnName = raceColumnName;
this.fleetName = fleetName;
competitorRegistrationInRaceLogCheckBox = new CheckBox(stringMessages.registerCompetitorsOnRace());
fleetNameWithCompetitors = findCompetitorsFromTheSameRaceColumn(fleets);
competitorRegistrationInRaceLogCheckBox = createCheckbox(stringMessages.registerCompetitorsOnRace());
validator.setCompetitorRegistrationInRaceLogCheckBox(competitorRegistrationInRaceLogCheckBox);
validator.setFleetWithCompetitors(fleetNameWithCompetitors);
setupCompetitorRegistationsOnRaceCheckbox();
}
/**
* For all {@code fleets} passed and not equal to {@link #fleetName} retrieves the competitor-per-race registrations
* for the {@link #raceColumnName}.
*/
private Map<String, Set<CompetitorDTO>> findCompetitorsFromTheSameRaceColumn(final List<FleetDTO> fleets) {
final Map<String, Set<CompetitorDTO>> result = new HashMap<>();
final Map<String, ParallelExecutionCallback<Collection<CompetitorDTO>>> callbacksForFleetNames = new HashMap<>();
for (FleetDTO fleetDTO : fleets) {
final String curFleetName = fleetDTO.getName();
if (!curFleetName.equals(fleetName)) {
callbacksForFleetNames.put(curFleetName, new ParallelExecutionCallback<Collection<CompetitorDTO>>() {
@Override
public void onSuccess(Collection<CompetitorDTO> competitorRegistrationsForRace) {
result.put(curFleetName, new HashSet<>(competitorRegistrationsForRace));
super.onSuccess(competitorRegistrationsForRace);
}
});
}
}
new ParallelExecutionHolder(callbacksForFleetNames.values().toArray(new ParallelExecutionCallback<?>[0])) {
@Override
protected void handleSuccess() {
// if the data was gained completely then gray out specific rows
grayOutRows();
}
@Override
protected void handleFailure(Throwable t) {
errorReporter.reportError(
"Could not load already registered competitors: " + t.getMessage());
}
};
for (final Entry<String, ParallelExecutionCallback<Collection<CompetitorDTO>>> fleetNameAndCallback : callbacksForFleetNames.entrySet()) {
sailingService.getCompetitorRegistrationsForRace(leaderboardName, raceColumnName, fleetNameAndCallback.getKey(), fleetNameAndCallback.getValue());
}
return result;
}
/**
* Grays out rows with competitors from the same race column
*/
private void grayOutRows() {
List<CompetitorWithToolTipDTO> competitors = new ArrayList<>();
for (Map.Entry<String, Set<CompetitorDTO>> entry : fleetNameWithCompetitors.entrySet()) {
if (!entry.getKey().equals(fleetName)) {
for (CompetitorDTO competitor : entry.getValue()) {
competitors.add(new CompetitorWithToolTipDTO(competitor, stringMessages
.competitorToolTipMessage(competitor.getName(), fleetName, entry.getKey(), raceColumnName)));
}
}
}
allCompetitorsTable.grayOutCompetitors(competitors);
registeredCompetitorsTable.grayOutCompetitors(competitors);
}
@Override
protected void setRegisteredCompetitors() {
if (showOnlyCompetitorsOfLog()) {
@@ -77,6 +174,7 @@ public class RaceLogCompetitorRegistrationDialog extends AbstractCompetitorRegis
@Override
public void onSuccess(Collection<CompetitorDTO> registeredCompetitors) {
move(allCompetitorsTable, registeredCompetitorsTable, registeredCompetitors);
validate();
}
@Override
@@ -91,6 +189,7 @@ public class RaceLogCompetitorRegistrationDialog extends AbstractCompetitorRegis
@Override
public void onSuccess(Collection<CompetitorDTO> registeredCompetitors) {
move(allCompetitorsTable, registeredCompetitorsTable, registeredCompetitors);
validate();
}
@Override
@@ -26,6 +26,7 @@ public class RegattaLogCompetitorRegistrationDialog extends AbstractCompetitorRe
@Override
public void onSuccess(Collection<CompetitorDTO> registeredCompetitors) {
move(allCompetitorsTable, registeredCompetitorsTable, registeredCompetitors);
validate();
}
@Override
@@ -41,6 +42,7 @@ public class RegattaLogCompetitorRegistrationDialog extends AbstractCompetitorRe
@Override
public void onSuccess(Collection<CompetitorDTO> registeredCompetitors) {
move(allCompetitorsTable, registeredCompetitorsTable, registeredCompetitors);
validate();
}
@Override
@@ -284,6 +284,7 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC
public void showWithBoatClass(String boatClassName) {
new RaceLogCompetitorRegistrationDialog(boatClassName, sailingService, stringMessages,
errorReporter, editable, leaderboardName, raceColumnName, fleetName,
raceColumnDTOAndFleetDTO.getA().getFleets(),
new DialogCallback<Set<CompetitorDTO>>() {
@Override
@@ -44,7 +44,7 @@ public abstract class TableWrapper<T, S extends RefreshableSelectionModel<T>> im
private SelectionCheckboxColumn<T> selectionCheckboxColumn;
private final EntityIdentityComparator<T> entityIdentityComparator;
private final AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class);
protected final AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class);
private final ListHandler<T> columnSortHandler;
@Override
@@ -3,7 +3,7 @@ package com.sap.sailing.gwt.ui.client;
import com.google.gwt.user.client.rpc.AsyncCallback;
/**
* The class is used together with the #{ParallelExecutionHolder} when two or more asynchronous service calls should run in parallel
* The class is used together with the {@link ParallelExecutionHolder} when two or more asynchronous service calls should run in parallel
* and only in case all of them has been executed successfully the next action makes sense.
*/
public class ParallelExecutionCallback<T> implements AsyncCallback<T> {
@@ -1501,6 +1501,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String warningDirectCompetitorRegistration();
String warningRegattaCompetitorRegistration();
String competitorRegistrationsOnRaceDisabled();
String warningForDisabledCompetitors(String competitorsSet);
String competitorToolTipMessage(String competitorName, String currentFleetName, String assignedToFleet, String raceName);
String addMarkToRegatta();
String selectALeaderboardGroup();
@Override
@@ -1470,6 +1470,8 @@ doYouWantToDisableCompetitorsRegistrationsOnTheRace=Do you want to disable compe
warningDirectCompetitorRegistration=Warning! This setting will lead to competitors for this race being registered on a per race basis. Competitors will not be automatically registered for this race, when registering them on the corresponding regatta.
warningRegattaCompetitorRegistration=Warning! This will reactivate competior registrations on the regatta. All competitor registrations from the regatta will automatically be used as competitor registration for this race.
competitorRegistrationsOnRaceDisabled=Competitor Registrations are disabled for this race. Enable by checking the checkbox to the left.
warningForDisabledCompetitors=The following competitors cannot be registered to this race : {0}
competitorToolTipMessage={0} has already been assigned to fleet {2} in race {3} and therefore cannot be assigned to fleet {1} within the same race
addMarkToRegatta=Add Mark to Regatta
selectALeaderboardGroup=Select a leaderboard group...
pleaseSelect=Please select
@@ -1456,6 +1456,8 @@ warningDirectCompetitorRegistration=Warnung! Diese Einstellung führt dazu, dass
warningRegattaCompetitorRegistration=Warnung! Diese Einstellung reaktiviert die Teilnehmerregistrierung auf Regatta-Ebene. Alle Teilnehmer der zu diesem Rennen gehörenden Regatta werden automatisch Teilnehmer dieses Rennens.
doYouWantToDisableCompetitorsRegistrationsOnTheRace=Wollen Sie Teilnehmer auf Regatta- statt Rennebene registrieren?
competitorRegistrationsOnRaceDisabled=Teilnehmeranmeldung ist für dieses Rennen auf Rennebene deaktiviert. Aktivieren durch selektieren der CheckBox auf der linken Seite.
warningForDisabledCompetitors=Die folgenden Teilnehmer konnten nicht für diese Wettfahrt angemeldet werden: {0}
competitorToolTipMessage=Teilnehmer {0} ist bereits der Gruppe {2} in der Wettfahrt {3} zugewiesen und kann daher nicht der Gruppe {1} in der selben Wettfahrt zugewiesen werden
addMarkToRegatta=Tonne zu Regatta hinzufügen
selectALeaderboardGroup=Bitte eine Ranglisten-Gruppe auswählen...
pleaseSelect=Bitte auswählen
@@ -50,7 +50,6 @@ import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.ImageResourceRenderer;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.CellPreviewEvent;
import com.google.gwt.view.client.ListDataProvider;
@@ -112,6 +111,7 @@ import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator;
import com.sap.sse.gwt.client.controls.busyindicator.BusyStateChangeListener;
import com.sap.sse.gwt.client.controls.busyindicator.BusyStateProvider;
import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator;
import com.sap.sse.gwt.client.panels.OverlayAssistantScrollPanel;
import com.sap.sse.gwt.client.player.PlayStateListener;
import com.sap.sse.gwt.client.player.TimeListener;
import com.sap.sse.gwt.client.player.Timer;
@@ -288,7 +288,7 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
*/
private final RegattaAndRaceIdentifier preSelectedRace;
private final VerticalPanel contentPanel;
private final FlowPanel contentPanel;
private HorizontalPanel refreshAndSettingsPanel;
private Label scoreCorrectionLastUpdateTimeLabel;
@@ -366,6 +366,12 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
private final boolean showCompetitorFilterStatus;
private CompetitorFilterPanel competitorFilterPanel;
/**
* Whether or not a second scroll bar, synchronized with the invisible native scroll bar, shall appear at
* the bottom of the viewport. See {@link OverlayAssistantScrollPanel}.
*/
private final boolean enableSyncedScroller;
protected StringMessages getStringMessages() {
return stringMessages;
@@ -401,7 +407,7 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
return isEmbedded;
}
public VerticalPanel getContentPanel() {
public FlowPanel getContentPanel() {
return contentPanel;
}
@@ -1752,7 +1758,8 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
PlayModes.Live, PlayStates.Paused, /* delayBetweenAutoAdvancesInMilliseconds */ LeaderboardEntryPoint.DEFAULT_REFRESH_INTERVAL_MILLIS), leaderboardGroupName,
leaderboardName, errorReporter, stringMessages, userAgent, showRaceDetails,
/* competitorSearchTextBox */ null, /* showSelectionCheckbox */ true, /* optionalRaceTimesInfoProvider */ null,
/* autoExpandLastRaceColumn */ false, /* adjustTimerDelay */ true, /*autoApplyTop30Filter*/ false, false);
/* autoExpandLastRaceColumn */ false, /* adjustTimerDelay */ true, /* autoApplyTopNFilter */ false,
/* showCompetitorFilterStatus */ false, /* enableSyncScroller */ false);
}
public LeaderboardPanel(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor,
@@ -1761,7 +1768,8 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
String leaderboardName, final ErrorReporter errorReporter, final StringMessages stringMessages,
final UserAgentDetails userAgent, boolean showRaceDetails, CompetitorFilterPanel competitorSearchTextBox,
boolean showSelectionCheckbox, RaceTimesInfoProvider optionalRaceTimesInfoProvider,
boolean autoExpandLastRaceColumn, boolean adjustTimerDelay, boolean autoApplyTopNFilter, boolean showCompetitorFilterStatus) {
boolean autoExpandLastRaceColumn, boolean adjustTimerDelay, boolean autoApplyTopNFilter,
boolean showCompetitorFilterStatus, boolean enableSyncScroller) {
this.showSelectionCheckbox = showSelectionCheckbox;
this.showRaceDetails = showRaceDetails;
this.sailingService = sailingService;
@@ -1781,6 +1789,7 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
this.adjustTimerDelay = adjustTimerDelay;
this.initialCompetitorFilterHasBeenApplied = !autoApplyTopNFilter;
this.showCompetitorFilterStatus = showCompetitorFilterStatus;
this.enableSyncedScroller = enableSyncScroller;
overallDetailColumnMap = createOverallDetailColumnMap();
settingsUpdatedExplicitly = !settings.isUpdateUponPlayStateChange();
raceNameForDefaultSorting = settings.getNameOfRaceToSort();
@@ -1840,7 +1849,7 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
});
leaderboardTable.ensureDebugId("LeaderboardCellTable");
selectionCheckboxColumn = new LeaderboardSelectionCheckboxColumn(competitorSelectionProvider);
getLeaderboardTable().setWidth("100%");
leaderboardTable.setWidth("100%");
leaderboardSelectionModel = new MultiSelectionModel<LeaderboardRowDTO>();
// remember handler registration so we can temporarily remove it and re-add it to suspend selection events while we're actively changing it
selectionChangeHandler = new Handler() {
@@ -1865,7 +1874,7 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
setShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(settings.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor());
SimplePanel mainPanel = new SimplePanel();
contentPanel = new VerticalPanel();
contentPanel = new FlowPanel();
leaderboardTable.getElement().getStyle().setMarginTop(10, Unit.PX);
contentPanel.setStyleName(STYLE_LEADERBOARD_CONTENT);
busyIndicator = new SimpleBusyIndicator(false, 0.8f);
@@ -1888,11 +1897,13 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
});
this.competitorFilterPanel = competitorSearchTextBox;
}
SortedCellTable<LeaderboardRowDTO> leaderboardTable = getLeaderboardTable();
// leaderboardTable.getElement().getStyle().setMarginTop(5, Unit.PX);
filterControlPanel = new HorizontalPanel();
filterControlPanel.setStyleName("LeaderboardPanel-FilterControl-Panel");
contentPanel.add(leaderboardTable);
if (enableSyncedScroller) {
contentPanel.add(new OverlayAssistantScrollPanel(leaderboardTable));
} else {
contentPanel.add(leaderboardTable);
}
if (showCompetitorFilterStatus) {
contentPanel.add(createFilterDeselectionControl());
}
@@ -1902,7 +1913,6 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
if (timer.isInitialized()) {
loadCompleteLeaderboard(/* showProgress */ false);
}
}
private Widget createToolbarPanel() {
@@ -47,8 +47,9 @@ public class LeaderboardViewer extends AbstractLeaderboardViewer {
super(competitorSelectionModel, asyncActionsExecutor, timer, stringMessages, hideToolbar, new LeaderboardPanel(
sailingService, asyncActionsExecutor, leaderboardSettings, preselectedRace != null, preselectedRace,
competitorSelectionModel, timer, leaderboardGroupName, leaderboardName, errorReporter,
stringMessages, userAgent, showRaceDetails, /* competitorSearchTextBox */ null, /* showSelectionCheckbox */ true, /* adjustTimerDelay */
/* raceTimesInfoProvider */null, autoExpandLastRaceColumn, true, /*autoApplyTopNFilter*/ false, false));
stringMessages, userAgent, showRaceDetails, /* competitorSearchTextBox */ null, /* showSelectionCheckbox */ true,
/* raceTimesInfoProvider */ null, autoExpandLastRaceColumn, /* adjustTimerDelay */ true, /* autoApplyTopNFilter */ false,
/* showCompetitorFilterStatus */ false, /* enableSyncScroller */ false));
final FlowPanel mainPanel = createViewerPanel();
setWidget(mainPanel);
multiCompetitorChart = new MultiCompetitorLeaderboardChart(sailingService, asyncActionsExecutor, leaderboardName, chartDetailType,
@@ -79,7 +80,8 @@ public class LeaderboardViewer extends AbstractLeaderboardViewer {
leaderboardSettings, preselectedRace != null, preselectedRace, competitorSelectionProvider, timer,
leaderboardGroupName, overallLeaderboardName, errorReporter, stringMessages, userAgent,
false, /* competitorSearchTextBox */ null, /* showSelectionCheckbox */ true, /* raceTimesInfoProvider */null,
false, /* adjustTimerDelay */ true, /*autoApplyTopNFilter*/ false, false);
false, /* adjustTimerDelay */ true, /* autoApplyTopNFilter */ false,
/* showCompetitorFilterStatus */ false, /* enableSyncScroller */ false);
mainPanel.add(overallLeaderboardPanel);
addComponentToNavigationMenu(overallLeaderboardPanel, true, stringMessages.seriesLeaderboard(),
/* hasSettingsWhenComponentIsInvisible*/ true);
@@ -48,7 +48,8 @@ public class MetaLeaderboardViewer extends AbstractLeaderboardViewer {
leaderboardSettings, preselectedRace != null, preselectedRace, competitorSelectionModel, timer,
leaderboardGroupName, metaLeaderboardName, errorReporter, stringMessages, userAgent,
showRaceDetails, /* competitorSearchTextBox */ null, /* showSelectionCheckbox */ true, /* raceTimesInfoProvider */null,
autoExpandLastRaceColumn, /* adjustTimerDelay */ true, /*autoApplyTopNFilter*/ false, false));
autoExpandLastRaceColumn, /* adjustTimerDelay */ true, /* autoApplyTopNFilter */ false,
/* showCompetitorFilterStatus */ false, /* enableSyncScroller */ false));
FlowPanel mainPanel = createViewerPanel();
setWidget(mainPanel);
final Label overallStandingsLabel = new Label(stringMessages.overallStandings());
@@ -228,7 +228,8 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent<LeaderboardSett
/* preselectedRace*/ null, new CompetitorSelectionModel(true), timer,
null, newSelectedLeaderboardName, errorReporter, stringMessages, userAgent,
showRaceDetails, /* competitorSearchTextBox */ null, /* showSelectionCheckbox */ true, /* raceTimesInfoProvider */null,
false, /* adjustTimerDelay */ true, /*autoApplyTopNFilter*/ false, false);
false, /* adjustTimerDelay */ true, /* autoApplyTopNFilter */ false,
/* showCompetitorFilterStatus */ false, /* enableSyncScroller */ false);
selectedLeaderboardFlowPanel.add(newSelectedLeaderboardPanel);
for (LeaderboardUpdateListener listener : leaderboardUpdateListeners) {
newSelectedLeaderboardPanel.addLeaderboardUpdateListener(listener);
@@ -459,7 +459,7 @@ public class RaceBoardPanel extends AbstractPerspectiveComposite<RaceBoardPerspe
userAgent, /* showRaceDetails */ true, competitorSearchTextBox,
/* showSelectionCheckbox */ true, raceTimesInfoProvider, /* autoExpandLastRaceColumn */ false,
/* don't adjust the timer's delay from the leaderboard; control it solely from the RaceTimesInfoProvider */ false,
/*autoApplyTopNFilter*/ false, /*showCompetitorFilterStatus*/ false);
/* autoApplyTopNFilter */ false, /* showCompetitorFilterStatus */ false, /* enableSyncScroller */ false);
}
private void setComponentVisible(SideBySideComponentViewer componentViewer, Component<?> component, boolean visible) {
Binary file not shown.
@@ -0,0 +1,29 @@
package com.sap.sailing.polars.jaxrs.api.test;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.polars.ReplicablePolarService;
import com.sap.sailing.polars.jaxrs.client.PolarDataClient;
public class PolarDataClientMock extends PolarDataClient {
private final File file;
public PolarDataClientMock(File file, ReplicablePolarService polarService, DomainFactory domainFactory) {
super(null, polarService);
polarService.registerDomainFactory(domainFactory);
this.file = file;
}
@Override
protected InputStream getContentFromResponse() throws IOException, ParseException {
return new FileInputStream(file);
}
}
@@ -0,0 +1,71 @@
package com.sap.sailing.polars.jaxrs.api.test;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
import org.json.simple.parser.ParseException;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.impl.DomainFactoryImpl;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.polars.impl.PolarDataServiceImpl;
public class PolarDataResourceTest {
private static final Logger logger = Logger.getLogger(PolarDataResourceTest.class.getName());
private static final double[] SPEED_FUNCTIONS_COEFFS_DOWNWIND =
new double[]{ 0, 2.2378615205516326, -0.0801870828343283, 9.81959605693472E-4 };
private static final double[] ANGLE_FUNCTION_COEFFS_DOWNWIND =
new double[]{ 138.3296034098894, -5.908558698662091, 1.1870404653964215, -0.056603488833331994 };
private static final String BOAT_CLASS = "GC32";
private PolarDataServiceImpl polarService;
private DomainFactory domainFactory;
@Before
public void setUp() throws IOException, ParseException, ClassNotFoundException, InterruptedException {
polarService = new PolarDataServiceImpl();
domainFactory = new DomainFactoryImpl(/* raceLogResolver */ null);
final PolarDataClientMock client = new PolarDataClientMock(new File("resources/polar_data"), polarService, domainFactory);
client.updatePolarDataRegressions();
// ensure that setting the domain factory has worked
polarService.runWithDomainFactory(domainFactory -> {
try {
client.updatePolarDataRegressions();
} catch (Exception e) {
logger.log(Level.SEVERE, "Exception while trying to import polar data from file during test", e);
}
});
}
/**
* Test to check if client importing data correctly. Using {@link PolarDataClientMock} which use {@link File}
* polar_data.json as source
*
* @throws NotEnoughDataHasBeenAddedException
*/
@Test
public void testImportingFromMockFile() throws NotEnoughDataHasBeenAddedException {
BoatClass boatClass = domainFactory.getOrCreateBoatClass(BOAT_CLASS);
PolynomialFunction angleDownwindFunction = new PolynomialFunction(ANGLE_FUNCTION_COEFFS_DOWNWIND);
PolynomialFunction speedDownwindFunction = new PolynomialFunction(SPEED_FUNCTIONS_COEFFS_DOWNWIND);
assertThat(polarService.getSpeedRegressionsPerAngle().size(), is(68));
assertThat(polarService.getCubicRegressionsPerCourse().size(), is(4));
assertThat(polarService.getFixCointPerBoatClass().get(boatClass), is(9330L));
// presuming that if downwind functions & regression collections' size are correct then any other thing is
// imported correctly
assertThat(polarService.getAngleRegressionFunction(boatClass, LegType.DOWNWIND), is(angleDownwindFunction));
assertThat(polarService.getSpeedRegressionFunction(boatClass, LegType.DOWNWIND), is(speedDownwindFunction));
}
}
+1
View File
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="resources"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="output" path="bin"/>
@@ -11,20 +11,42 @@ Export-Package:
com.sap.sailing.polars.regression,
com.sap.sailing.polars.windestimation
Require-Bundle:
com.sap.sailing.domain.common;bundle-version="1.0.0",
com.sap.sailing.domain.common,
com.sap.sailing.domain,
com.sap.sailing.polars.datamining,
com.sap.sailing.server.gateway.serialization.shared.android,
com.sap.sailing.server.gateway.serialization,
com.sap.sse.datamining,
com.sap.sse.datamining.annotations;bundle-version="1.0.0",
com.sap.sse.datamining.shared;bundle-version="1.0.0",
com.sap.sse.datamining.annotations,
com.sap.sse.datamining.shared,
com.sap.sse.common,
com.sap.sse,
com.sap.sse.shared.android,
org.apache.commons.math;bundle-version="2.1.0",
org.eclipse.osgi,
com.sap.sse.replication;bundle-version="1.0.0",
com.sap.sse.operationaltransformation;bundle-version="1.0.0"
com.sap.sse.replication,
com.sap.sse.operationaltransformation,
com.sap.sse.security,
com.sun.jersey,
javax.servlet;bundle-version="2.6.0",
org.apache.shiro.core;bundle-version="1.2.2",
org.apache.shiro.web;bundle-version="1.2.2",
org.apache.shiro.ehcache;bundle-version="1.2.3",
org.eclipse.jetty.websocket.api;bundle-version="9.0.4",
org.eclipse.jetty.websocket.server;bundle-version="9.0.4",
org.eclipse.jetty.websocket.servlet;bundle-version="9.0.4",
org.eclipse.jetty.websocket.client;bundle-version="9.0.4",
org.eclipse.jetty.websocket.common;bundle-version="9.0.4"
Import-Package:
com.sap.sailing.util,
com.sap.sse.common
javax.ws.rs;version="1.1.1",
javax.ws.rs.core;version="1.1.1",
org.apache.http,
org.apache.http.client,
org.apache.http.client.methods;version="4.2.6",
org.apache.http.impl.client;version="4.2.6",
org.json.simple,
org.json.simple.parser
Web-ContextPath: /polars
Bundle-Activator: com.sap.sailing.polars.impl.Activator
Bundle-ActivationPolicy: lazy
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<!-- Disable directory listing: -->
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class>
<init-param>
<param-name>dirAllowed</param-name>
<param-value>false</param-value>
</init-param>
</servlet>
<!-- Apache Shiro -->
<context-param>
<param-name>shiroEnvironmentClass</param-name>
<param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value>
</context-param>
<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>Jersey REST API</servlet-name>
<servlet-class>com.sap.sailing.polars.jaxrs.RestServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.sap.sailing.polars.jaxrs.api.RestApiApplication</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
<param-value>com.sap.sailing.polars.jaxrs.AccessControlAndEncodingResponseFilter</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.sap.sailing.polars.jaxrs</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey REST API</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
</web-app>
+2 -1
View File
@@ -1,4 +1,5 @@
source.. = src/
source.. = src/,\
resources/
output.. = bin/
bin.includes = META-INF/,\
.
@@ -0,0 +1,72 @@
[main]
credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
credentialsMatcher.storedCredentialsHexEncoded = false
credentialsMatcher.hashIterations = 1024
# Realm configuration:
# --------------------
permissionsForRoleProvider = com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider
upRealm = com.sap.sse.security.UsernamePasswordRealm
upRealm.credentialsMatcher = $credentialsMatcher
upRealm.permissionsForRoleProvider = $permissionsForRoleProvider
oauthRealm = com.sap.sse.security.OAuthRealm
oauthRealm.permissionsForRoleProvider = $permissionsForRoleProvider
bearerTokenRealm = com.sap.sse.security.BearerTokenRealm
bearerTokenRealm.permissionsForRoleProvider = $permissionsForRoleProvider
# 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
# 24h timeout
securityManager.sessionManager.globalSessionTimeout = 86400000
cacheManager = com.sap.sse.security.SessionCacheManager
securityManager.cacheManager = $cacheManager
# Authentication Filter Configurations
# ------------------------------------
# A filter that requires the user to have at least one of the roles specified as
# filter parameter. This makes it different from the default roles[...] filter
# which requires the user to have ALL of the roles specified as parameters.
anyofroles = com.sap.sse.security.AnyOfRolesFilter
# A custom filter for GWT pages that require an authenticated user.
# Forwards unauthenticated users to the default login page. When
# addressed directly (and not based on a redirect), users who logged
# on successfully will be redirected to the UserManagement.html page
# where they can adjust their profile.
customGwt = com.sap.sse.security.CustomFilter
customGwt.loginUrl = /security/ui/Login.html
customGwt.successUrl = /UserManagement.html
# Configuration for the default shiro HTTP form authentication filter.
# It assumes that form-based login parameters are sent to the
# /api/restsecurity/login RESTlet and forwards successful login attempts
# to the /api/restsecurity/hello service which is expected to respond
# with a JSON document containing the authenticated subject's properties.
authc.loginUrl = /api/restsecurity/login
authc.successUrl = /api/restsecurity/hello
# 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]
# Uncomment the following to require the permission "event:view" in order to use /api/v1/events:
#/api/v1/events = bearerToken
# Example: Secure access to leaderboard race column factor updates; read access should require no permissions
#/api/v1/leaderboards/*/racecolumnfactors/** = bearerToken
# Example: Secure the access (in particular the POST/write access) to posting race log events
#/rc/racelog = bearerToken
@@ -1,6 +1,6 @@
package com.sap.sailing.polars;
import com.sap.sailing.polars.impl.PolarDataServiceImpl;
import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sse.replication.OperationWithResult;
/**
@@ -11,6 +11,6 @@ import com.sap.sse.replication.OperationWithResult;
*
* @param <T>
*/
public interface PolarDataOperation<T> extends OperationWithResult<PolarDataServiceImpl, T> {
public interface PolarDataOperation<T> extends OperationWithResult<PolarDataService, T> {
}
@@ -0,0 +1,12 @@
package com.sap.sailing.polars;
import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sse.replication.impl.ReplicableWithObjectInputStream;
public interface ReplicablePolarService extends PolarDataService, ReplicableWithObjectInputStream<PolarDataService, PolarDataOperation<?>> {
/**
* Should something go wrong during initial load, using this method a caller can reset this service into a fresh,
* empty and valid state.
*/
void resetState();
}
@@ -4,6 +4,7 @@ import java.util.Dictionary;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.osgi.framework.BundleActivator;
@@ -11,6 +12,8 @@ import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sailing.polars.ReplicablePolarService;
import com.sap.sailing.polars.jaxrs.client.PolarDataClient;
import com.sap.sse.replication.Replicable;
/**
@@ -21,6 +24,8 @@ import com.sap.sse.replication.Replicable;
*/
public class Activator implements BundleActivator {
private static final String POLAR_DATA_SOURCE_URL_PROPERTY_NAME = "polardata.source.url";
private static final Logger logger = Logger.getLogger(Activator.class.getName());
private final Set<ServiceRegistration<?>> registrations = new HashSet<>();
@@ -33,6 +38,38 @@ public class Activator implements BundleActivator {
final Dictionary<String, String> replicableServiceProperties = new Hashtable<>();
replicableServiceProperties.put(Replicable.OSGi_Service_Registry_ID_Property_Name, service.getId().toString());
registrations.add(context.registerService(Replicable.class.getName(), service, replicableServiceProperties));
final String polarDataSourceURL = System.getProperty(POLAR_DATA_SOURCE_URL_PROPERTY_NAME);
if (polarDataSourceURL != null && !polarDataSourceURL.isEmpty()) {
waitForRacingEventServiceToObtainDomainFactory(polarDataSourceURL, service);
}
}
/**
* Spawns a daemon thread that waits for the domain factory to be registered with the {@link PolarDataService}, then
* runs the polar data import from the given URL. The domain factory is required to resolve boat classes during
* de-serialization.
*/
private void waitForRacingEventServiceToObtainDomainFactory(final String polarDataSourceURL, final ReplicablePolarService polarService) {
final Thread t = new Thread(()->{
try {
Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); // ensure that classpath:... Shiro ini files are resolved properly
logger.info("Waiting for domain factory to be registered with PolarService...");
// Note: although the domainFactory parameter isn't used, using runWithDomainFactory ensures that the domain factory is there
polarService.runWithDomainFactory(domainFactory -> {
PolarDataClient polarDataClient = new PolarDataClient(polarDataSourceURL, polarService);
try {
polarDataClient.updatePolarDataRegressions();
} catch (Exception e) {
logger.log(Level.SEVERE, "Exception while trying to import polar data from "+polarDataSourceURL, e);
}
});
} catch (InterruptedException e) {
logger.log(Level.SEVERE, "Interrupted while waiting for UserStore service", e);
}
}, "PolarService activator waiting for domain factory to be registered");
t.setDaemon(true);
t.start();
}
@Override
@@ -7,9 +7,11 @@ import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;
import java.util.logging.Logger;
import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
@@ -37,18 +39,20 @@ import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sailing.domain.polars.PolarsChangedListener;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.polars.PolarDataOperation;
import com.sap.sailing.polars.ReplicablePolarService;
import com.sap.sailing.polars.mining.AngleAndSpeedRegression;
import com.sap.sailing.polars.mining.BearingClusterGroup;
import com.sap.sailing.polars.mining.CubicRegressionPerCourseProcessor;
import com.sap.sailing.polars.mining.PolarDataMiner;
import com.sap.sailing.polars.mining.SpeedRegressionPerAngleClusterProcessor;
import com.sap.sailing.polars.regression.impl.IncrementalAnyOrderLeastSquaresImpl;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.datamining.data.ClusterGroup;
import com.sap.sse.datamining.shared.GroupKey;
import com.sap.sse.replication.OperationExecutionListener;
import com.sap.sse.replication.OperationWithResult;
import com.sap.sse.replication.ReplicationMasterDescriptor;
import com.sap.sse.replication.impl.OperationWithResultWithIdWrapper;
import com.sap.sse.replication.impl.ReplicableWithObjectInputStream;
/**
* Uses a custom datamining pipeline to aggregate incoming fixes in two regression based polar containers.
@@ -59,14 +63,13 @@ import com.sap.sse.replication.impl.ReplicableWithObjectInputStream;
* @author Axel Uhl
*
*/
public class PolarDataServiceImpl implements PolarDataService,
ReplicableWithObjectInputStream<PolarDataServiceImpl, PolarDataOperation<?>> {
public class PolarDataServiceImpl implements ReplicablePolarService {
private static final Logger logger = Logger.getLogger(PolarDataServiceImpl.class.getSimpleName());
private PolarDataMiner polarDataMiner;
private final ConcurrentMap<OperationExecutionListener<PolarDataServiceImpl>, OperationExecutionListener<PolarDataServiceImpl>> operationExecutionListeners;
private final ConcurrentMap<OperationExecutionListener<PolarDataService>, OperationExecutionListener<PolarDataService>> operationExecutionListeners;
/**
* The master from which this replicable is currently replicating, or <code>null</code> if this replicable is not
@@ -74,7 +77,7 @@ public class PolarDataServiceImpl implements PolarDataService,
*/
private ReplicationMasterDescriptor replicatingFromMaster;
private final Set<OperationWithResult<PolarDataServiceImpl, ?>> operationsSentToMasterForReplication;
private final Set<OperationWithResult<PolarDataService, ?>> operationsSentToMasterForReplication;
private ThreadLocal<Boolean> currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster = ThreadLocal
.withInitial(() -> false);
@@ -85,15 +88,18 @@ public class PolarDataServiceImpl implements PolarDataService,
* Constructs the polar data service with default generation settings.
*/
public PolarDataServiceImpl() {
resetState();
this.operationsSentToMasterForReplication = new HashSet<>();
this.operationExecutionListeners = new ConcurrentHashMap<>();
}
@Override
public void resetState() {
PolarSheetGenerationSettings settings = PolarSheetGenerationSettingsImpl.createBackendPolarSettings();
ClusterGroup<Bearing> angleClusterGroup = createAngleClusterGroup();
CubicRegressionPerCourseProcessor cubicRegressionPerCourseProcessor = new CubicRegressionPerCourseProcessor();
SpeedRegressionPerAngleClusterProcessor speedRegressionPerAngleClusterProcessor = new SpeedRegressionPerAngleClusterProcessor(
angleClusterGroup);
this.polarDataMiner = new PolarDataMiner(settings, cubicRegressionPerCourseProcessor,
speedRegressionPerAngleClusterProcessor, angleClusterGroup);
this.operationsSentToMasterForReplication = new HashSet<>();
this.operationExecutionListeners = new ConcurrentHashMap<>();
SpeedRegressionPerAngleClusterProcessor speedRegressionPerAngleClusterProcessor = new SpeedRegressionPerAngleClusterProcessor(angleClusterGroup);
this.polarDataMiner = new PolarDataMiner(settings, cubicRegressionPerCourseProcessor, speedRegressionPerAngleClusterProcessor, angleClusterGroup);
}
private ClusterGroup<Bearing> createAngleClusterGroup() {
@@ -260,12 +266,12 @@ public class PolarDataServiceImpl implements PolarDataService,
}
@Override
public void addOperationExecutionListener(OperationExecutionListener<PolarDataServiceImpl> listener) {
public void addOperationExecutionListener(OperationExecutionListener<PolarDataService> listener) {
operationExecutionListeners.put(listener, listener);
}
@Override
public void removeOperationExecutionListener(OperationExecutionListener<PolarDataServiceImpl> listener) {
public void removeOperationExecutionListener(OperationExecutionListener<PolarDataService> listener) {
operationExecutionListeners.remove(listener);
}
@@ -307,12 +313,8 @@ public class PolarDataServiceImpl implements PolarDataService,
public void initiallyFillFromInternal(ObjectInputStream is) throws IOException, ClassNotFoundException,
InterruptedException {
PolarSheetGenerationSettings backendPolarSettings = (PolarSheetGenerationSettings) is.readObject();
CubicRegressionPerCourseProcessor cubicRegressionPerCourseProcessor = (CubicRegressionPerCourseProcessor) is
.readObject();
SpeedRegressionPerAngleClusterProcessor speedRegressionPerAngleClusterProcessor = (SpeedRegressionPerAngleClusterProcessor) is
.readObject();
CubicRegressionPerCourseProcessor cubicRegressionPerCourseProcessor = (CubicRegressionPerCourseProcessor) is.readObject();
SpeedRegressionPerAngleClusterProcessor speedRegressionPerAngleClusterProcessor = (SpeedRegressionPerAngleClusterProcessor) is.readObject();
polarDataMiner = new PolarDataMiner(backendPolarSettings, cubicRegressionPerCourseProcessor,
speedRegressionPerAngleClusterProcessor, speedRegressionPerAngleClusterProcessor.getAngleCluster());
}
@@ -326,7 +328,7 @@ public class PolarDataServiceImpl implements PolarDataService,
}
@Override
public Iterable<OperationExecutionListener<PolarDataServiceImpl>> getOperationExecutionListeners() {
public Iterable<OperationExecutionListener<PolarDataService>> getOperationExecutionListeners() {
return operationExecutionListeners.keySet();
}
@@ -346,19 +348,35 @@ public class PolarDataServiceImpl implements PolarDataService,
}
@Override
public boolean hasSentOperationToMaster(OperationWithResult<PolarDataServiceImpl, ?> operation) {
public boolean hasSentOperationToMaster(OperationWithResult<PolarDataService, ?> operation) {
return this.operationsSentToMasterForReplication.contains(operation);
}
@Override
public void addOperationSentToMasterForReplication(
OperationWithResultWithIdWrapper<PolarDataServiceImpl, ?> operation) {
OperationWithResultWithIdWrapper<PolarDataService, ?> operation) {
this.operationsSentToMasterForReplication.add(operation);
}
@Override
public void registerDomainFactory(DomainFactory domainFactory) {
this.domainFactory = domainFactory;
synchronized(this) {
this.domainFactory = domainFactory;
this.notifyAll();
}
}
@Override
public void runWithDomainFactory(Consumer<DomainFactory> consumer) throws InterruptedException {
DomainFactory myDomainFactory;
synchronized(this) {
myDomainFactory = domainFactory;
while (myDomainFactory == null) {
wait();
myDomainFactory = domainFactory;
}
}
consumer.accept(myDomainFactory);
}
@Override
@@ -368,4 +386,15 @@ public class PolarDataServiceImpl implements PolarDataService,
}
}
public Map<GroupKey, AngleAndSpeedRegression> getCubicRegressionsPerCourse() {
return polarDataMiner.getCubicRegressionPerCourseProcessor().getRegressions();
}
public Map<GroupKey, IncrementalAnyOrderLeastSquaresImpl> getSpeedRegressionsPerAngle() {
return polarDataMiner.getSpeedRegressionPerAngleClusterProcessor().getRegressionsImpl();
}
public Map<BoatClass, Long> getFixCointPerBoatClass() {
return polarDataMiner.getSpeedRegressionPerAngleClusterProcessor().getFixCountPerBoatClass();
}
}
@@ -0,0 +1,22 @@
package com.sap.sailing.polars.jaxrs;
import javax.servlet.ServletContext;
import javax.ws.rs.core.Context;
import org.osgi.util.tracker.ServiceTracker;
import com.sap.sailing.polars.impl.PolarDataServiceImpl;
public abstract class AbstractPolarResource {
@Context
ServletContext servletContext;
// Working with concrete implementation not to create new dependencies between domain and datamining
// (PolarDataMiner is unavailable in com.sap.sailing.domain)
public PolarDataServiceImpl getPolarDataServiceImpl() {
@SuppressWarnings("unchecked")
ServiceTracker<PolarDataServiceImpl, PolarDataServiceImpl> tracker = (ServiceTracker<PolarDataServiceImpl, PolarDataServiceImpl>) servletContext
.getAttribute(RestServletContainer.POLAR_DATA_SERVICE_TRACKER_NAME);
return tracker.getService();
}
}
@@ -0,0 +1,20 @@
package com.sap.sailing.polars.jaxrs;
import javax.ws.rs.core.MultivaluedMap;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;
public class AccessControlAndEncodingResponseFilter implements ContainerResponseFilter {
@Override
public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
MultivaluedMap<String, Object> httpHeaders = response.getHttpHeaders();
httpHeaders.add("Access-Control-Allow-Origin", "*");
httpHeaders.add("Access-Control-Allow-Headers", "Authorization, Origin, X-Requested-With, Content-Type");
httpHeaders.add("Access-Control-Expose-Headers", "Location, Content-Disposition");
httpHeaders.add("Access-Control-Allow-Methods", "POST, PUT, GET, DELETE, HEAD, OPTIONS");
return response;
}
}
@@ -0,0 +1,52 @@
package com.sap.sailing.polars.jaxrs;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.ws.rs.core.Application;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
import com.sap.sailing.domain.polars.PolarDataService;
import com.sun.jersey.spi.container.servlet.ServletContainer;
public class RestServletContainer extends ServletContainer {
private static final long serialVersionUID = 3129261471808425410L;
private static final String OSGI_RFC66_WEBBUNDLE_BUNDLECONTEXT_NAME = "osgi-bundlecontext";
public static final String POLAR_DATA_SERVICE_TRACKER_NAME = "polarDataServiceTracker";
private ServiceTracker<PolarDataService, PolarDataService> polarDataServiceTracker;
public RestServletContainer() {
super();
}
public RestServletContainer(Application app) {
super(app);
}
public RestServletContainer(Class<? extends Application> appClass) {
super(appClass);
}
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
BundleContext context = (BundleContext) config.getServletContext().getAttribute(OSGI_RFC66_WEBBUNDLE_BUNDLECONTEXT_NAME);
polarDataServiceTracker = new ServiceTracker<PolarDataService, PolarDataService>(context, PolarDataService.class.getName(), null);
polarDataServiceTracker.open();
config.getServletContext().setAttribute(POLAR_DATA_SERVICE_TRACKER_NAME, polarDataServiceTracker);
}
@Override
public void destroy() {
super.destroy();
if (polarDataServiceTracker != null) {
polarDataServiceTracker.close();
}
}
}
@@ -0,0 +1,24 @@
package com.sap.sailing.polars.jaxrs.api;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import com.sap.sailing.polars.jaxrs.AbstractPolarResource;
@Path("/polar_data")
public class PolarDataResource extends AbstractPolarResource {
@GET
@Produces("application/octet-stream;charset=UTF-8")
public Response getRegressions() throws IOException {
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
getPolarDataServiceImpl().serializeForInitialReplication(bos);
bos.close();
return Response.ok(new ByteArrayInputStream(bos.toByteArray())).header("Content-Type", "application/octet-stream").build();
}
}
@@ -0,0 +1,21 @@
package com.sap.sailing.polars.jaxrs.api;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.core.Application;
import com.sap.sse.security.jaxrs.ShiroAuthorizationExceptionTo401ResponseMapper;
public class RestApiApplication extends Application {
public Set<Class<?>> getClasses() {
HashSet<Class<?>> classes = new HashSet<>();
// RESTlets
classes.add(PolarDataResource.class);
// Exception Mapper
classes.add(ShiroAuthorizationExceptionTo401ResponseMapper.class);
return classes;
}
}
@@ -0,0 +1,78 @@
package com.sap.sailing.polars.jaxrs.client;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.SystemDefaultHttpClient;
import org.json.simple.parser.ParseException;
import com.sap.sailing.polars.ReplicablePolarService;
import com.sap.sailing.polars.impl.PolarDataServiceImpl;
import com.sap.sailing.polars.mining.PolarDataMiner;
/**
* This class is used to replicate polar data regressions calculation from the remote server using Apache
* {@link HttpClient}
*
* @author Oleg_Zheleznov
* @author Axel Uhl (d043530)
*
*/
public class PolarDataClient {
private static final Logger logger = Logger.getLogger(PolarDataClient.class.getName());
private static final String RESOURCE = "polars/api/polar_data";
private final ReplicablePolarService polarDataServiceImpl;
private final String polarDataSourceURL;
/**
* Default constructor is missing because we need {@link PolarDataServiceImpl} to reach regressions
* @param polarDataSourceURL
* archive server URL string
* @param polarDataService
* {@link PolarDataServiceImpl} service to work with
*/
public PolarDataClient(String polarDataSourceURL, ReplicablePolarService polarDataService) {
this.polarDataServiceImpl = polarDataService;
this.polarDataSourceURL = polarDataSourceURL;
}
/**
* This method is used to update {@link PolarDataMiner} regressions with data received from remote server. Before
* the regression maps will be updated it may be cleaned
*/
public void updatePolarDataRegressions() throws IOException, ParseException, InterruptedException, ClassNotFoundException {
try {
logger.log(Level.INFO, "Loading polar regression data from remote server " + polarDataSourceURL);
final InputStream inputStream = getContentFromResponse();
polarDataServiceImpl.clearReplicaState();
polarDataServiceImpl.initiallyFillFrom(inputStream);
logger.log(Level.INFO,
"Loading polar regression data from remote server " + polarDataSourceURL + " succeeded");
} catch (Exception e) {
// Catching ClientProtocolException to indicate problems with HTTP protocol
logger.log(Level.WARNING, "Failed to load polar regression data from remote server " + polarDataSourceURL
+ ", " + e.getMessage()+"; resetting polar data miner");
polarDataServiceImpl.resetState();
}
}
private String getAPIString() {
return polarDataSourceURL + (polarDataSourceURL.endsWith("/") ? "" : "/") + RESOURCE;
}
protected InputStream getContentFromResponse() throws IOException, ParseException {
HttpClient client = new SystemDefaultHttpClient();
HttpGet getProcessor = new HttpGet(getAPIString());
HttpResponse processorResponse = client.execute(getProcessor);
return processorResponse.getEntity().getContent();
}
}
@@ -39,10 +39,27 @@ import com.sap.sse.common.Util.Pair;
public class AngleAndSpeedRegression implements Serializable {
private static final long serialVersionUID = 6343595388753945979L;
private final IncrementalLeastSquares speedRegression = new IncrementalAnyOrderLeastSquaresImpl(3, false);
private final IncrementalLeastSquares angleRegression = new IncrementalAnyOrderLeastSquaresImpl(3);
private final IncrementalLeastSquares speedRegression;
private final IncrementalLeastSquares angleRegression;
private double maxWindSpeedInKnots = -1;
private double maxWindSpeedInKnots;
public AngleAndSpeedRegression() {
speedRegression = new IncrementalAnyOrderLeastSquaresImpl(3, false);
angleRegression = new IncrementalAnyOrderLeastSquaresImpl(3);
maxWindSpeedInKnots = -1;
}
/**
* Constructor with parameters used by {@link AngleAndSpeedRegressionDeserializer} to deserialize regression data
* from remote server
*/
public AngleAndSpeedRegression(double maxWindSpeedInKnots, IncrementalLeastSquares speedRegression,
IncrementalLeastSquares angleRegression) {
this.speedRegression = speedRegression;
this.angleRegression = angleRegression;
this.maxWindSpeedInKnots = maxWindSpeedInKnots;
}
public void addData(WindWithConfidence<Pair<Position, TimePoint>> windSpeed,
BearingWithConfidence<Void> angleToTheWind, SpeedWithBearingWithConfidence<TimePoint> boatSpeed) {
@@ -116,4 +133,16 @@ public class AngleAndSpeedRegression implements Serializable {
return angleRegression.getOrCreatePolynomialFunction();
}
public IncrementalAnyOrderLeastSquaresImpl getSpeedRegression() {
return (IncrementalAnyOrderLeastSquaresImpl) speedRegression;
}
public IncrementalAnyOrderLeastSquaresImpl getAngleRegression() {
return (IncrementalAnyOrderLeastSquaresImpl) angleRegression;
}
public double getMaxWindSpeedInKnots() {
return maxWindSpeedInKnots;
}
}
@@ -1,6 +1,5 @@
package com.sap.sailing.polars.mining;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
@@ -58,14 +57,4 @@ public class BearingClusterGroup extends FixClusterGroup<Bearing> {
return new ComparatorClusterBoundary<Bearing>(angle, strategy, comparator);
}
private static class BearingComparator implements Comparator<Bearing>, Serializable {
private static final long serialVersionUID = 8166601046140275541L;
@Override
public int compare(Bearing arg0, Bearing arg1) {
return new Double(arg0.getDegrees()).compareTo(new Double(arg1.getDegrees()));
}
};
}
@@ -0,0 +1,17 @@
package com.sap.sailing.polars.mining;
import java.io.Serializable;
import java.util.Comparator;
import com.sap.sailing.domain.common.Bearing;
public class BearingComparator implements Comparator<Bearing>, Serializable {
private static final long serialVersionUID = 8166601046140275541L;
@Override
public int compare(Bearing arg0, Bearing arg1) {
return new Double(arg0.getDegrees()).compareTo(new Double(arg1.getDegrees()));
}
}
@@ -1,224 +1,237 @@
package com.sap.sailing.polars.mining;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger;
import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.domain.polars.PolarsChangedListener;
import com.sap.sse.datamining.components.AdditionalResultDataBuilder;
import com.sap.sse.datamining.components.Processor;
import com.sap.sse.datamining.factories.GroupKeyFactory;
import com.sap.sse.datamining.impl.components.GroupedDataEntry;
import com.sap.sse.datamining.shared.GroupKey;
/**
* Groups incoming fixes by boatclass and legtype into {@link AngleAndSpeedRegression} instances and
* provides access methods to regression data.
*
* @author D054528 (Frederik Petersen)
*
*/
public class CubicRegressionPerCourseProcessor implements
Processor<GroupedDataEntry<GPSFixMovingWithPolarContext>, Void>, Serializable {
private static final long serialVersionUID = 3059764273262382648L;
private static final Logger logger = Logger.getLogger(CubicRegressionPerCourseProcessor.class.getName());
private final Map<GroupKey, AngleAndSpeedRegression> regressions = new HashMap<>();
/**
* FIXME Make sure replication and listeners interact correctly
*/
private transient ConcurrentMap<BoatClass, Set<PolarsChangedListener>> listeners;
@Override
public boolean canProcessElements() {
// TODO Auto-generated method stub
return true;
}
@Override
public void processElement(GroupedDataEntry<GPSFixMovingWithPolarContext> element) {
GPSFixMovingWithPolarContext fix = element.getDataEntry();
if (fix.getLegType() == LegType.UPWIND || fix.getLegType() == LegType.DOWNWIND) {
GroupKey key = element.getKey();
AngleAndSpeedRegression regression;
synchronized (regressions) {
regression = regressions.get(key);
if (regression == null) {
regression = new AngleAndSpeedRegression();
regressions.put(key, regression);
}
}
regression.addData(fix.getWind(), fix.getAbsoluteAngleToTheWind(), fix.getBoatSpeed());
Set<PolarsChangedListener> listenersForBoatClass = listeners.get(fix.getBoatClass());
if (listenersForBoatClass != null) {
for (PolarsChangedListener listener : listenersForBoatClass) {
listener.polarsChanged();
}
}
}
}
/**
* Returns speed and twa for a given scenario, if data is available in the regression.
*/
public SpeedWithBearingWithConfidence<Void> getAverageSpeedAndCourseOverGround(BoatClass boatClass,
Speed windSpeed, LegType legType) throws NotEnoughDataHasBeenAddedException {
GroupKey key = createGroupKey(boatClass, legType);
SpeedWithBearingWithConfidence<Void> estimatedSpeedAndAngle = null;
if (regressions.containsKey(key)) {
estimatedSpeedAndAngle = regressions.get(key).estimateSpeedAndAngle(windSpeed);
} else {
throw new NotEnoughDataHasBeenAddedException("Not enough data has been added to Per Course Regressions");
}
return estimatedSpeedAndAngle;
}
/**
* Returns windspeed and windangle candidates for a given scenario, if enough data is available.
*/
public Set<SpeedWithBearingWithConfidence<Void>> estimateTrueWindSpeedAndAngleCandidates(BoatClass boatClass,
Speed speedOverGround, LegType legType, Tack tack) {
GroupKey key = createGroupKey(boatClass, legType);
Set<SpeedWithBearingWithConfidence<Void>> result = new HashSet<>();
if (regressions.containsKey(key)) {
try {
result = regressions.get(key).estimateTrueWindSpeedAndAngleCandidates(speedOverGround, legType, tack);
} catch (NotEnoughDataHasBeenAddedException e) {
// Return empty result
}
} else {
// Return empty result;
}
return result;
}
private GroupKey createGroupKey(final BoatClass boatClass, final LegType legType) {
LegTypePolarClusterKey key = new LegTypePolarClusterKey() {
@Override
public BoatClass getBoatClass() {
return boatClass;
}
@Override
public LegType getLegType() {
return legType;
}
};
GroupKey compoundKey;
try {
compoundKey = GroupKeyFactory.createNestingCompoundKeyFor(key, PolarDataDimensionCollectionFactory
.getCubicRegressionPerCourseClusterKeyDimensions());
} catch (NoSuchMethodException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
return compoundKey;
}
/**
* Allows direct access to speed regression functions for debugging purposes.
*
* For actual usage of the data please use
* {@link #getAverageSpeedAndCourseOverGround(BoatClass, Speed, LegType)}
* and
* {@link #estimateTrueWindSpeedAndAngleCandidates(BoatClass, Speed, LegType, Tack)}.
*/
public PolynomialFunction getSpeedRegressionFunction(BoatClass boatClass, LegType legType)
throws NotEnoughDataHasBeenAddedException {
GroupKey key = createGroupKey(boatClass, legType);
PolynomialFunction polynomialFunction;
if (regressions.containsKey(key)) {
polynomialFunction = regressions.get(key).getSpeedRegressionFunction();
} else {
throw new NotEnoughDataHasBeenAddedException();
}
return polynomialFunction;
}
/**
* Allows direct access to angle regression functions for debugging purposes.
*
* For actual usage of the data please use
* {@link #getAverageSpeedAndCourseOverGround(BoatClass, Speed, LegType)}
* and
* {@link #estimateTrueWindSpeedAndAngleCandidates(BoatClass, Speed, LegType, Tack)}.
*/
public PolynomialFunction getAngleRegressionFunction(BoatClass boatClass, LegType legType)
throws NotEnoughDataHasBeenAddedException {
GroupKey key = createGroupKey(boatClass, legType);
PolynomialFunction polynomialFunction;
if (regressions.containsKey(key)) {
polynomialFunction = regressions.get(key).getAngleRegressionFunction();
} else {
throw new NotEnoughDataHasBeenAddedException();
}
return polynomialFunction;
}
@Override
public void onFailure(Throwable failure) {
failure.printStackTrace();
logger.severe("Polar Data Mining Pipe failed. Cause: " + failure.getMessage());
throw new RuntimeException("Polar Data Miner failed.", failure);
}
public void setListeners(ConcurrentMap<BoatClass, Set<PolarsChangedListener>> listeners) {
this.listeners = listeners;
}
@Override
public Class<GroupedDataEntry<GPSFixMovingWithPolarContext>> getInputType() {
// TODO Auto-generated method stub
return null;
}
@Override
public Class<Void> getResultType() {
// No result type here, since this is a special case of a processor. It's the end of the pipe so to say.
return null;
}
@Override
public void finish() throws InterruptedException {
// Nothing to do here
}
@Override
public boolean isFinished() {
return false;
}
@Override
public void abort() {
// TODO Auto-generated method stub
}
@Override
public boolean isAborted() {
// TODO Auto-generated method stub
return false;
}
@Override
public AdditionalResultDataBuilder getAdditionalResultData(AdditionalResultDataBuilder additionalDataBuilder) {
// TODO Auto-generated method stub
return null;
}
}
package com.sap.sailing.polars.mining;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger;
import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.domain.polars.PolarsChangedListener;
import com.sap.sse.datamining.components.AdditionalResultDataBuilder;
import com.sap.sse.datamining.components.Processor;
import com.sap.sse.datamining.factories.GroupKeyFactory;
import com.sap.sse.datamining.impl.components.GroupedDataEntry;
import com.sap.sse.datamining.shared.GroupKey;
/**
* Groups incoming fixes by boatclass and legtype into {@link AngleAndSpeedRegression} instances and
* provides access methods to regression data.
*
* @author D054528 (Frederik Petersen)
*
*/
public class CubicRegressionPerCourseProcessor implements
Processor<GroupedDataEntry<GPSFixMovingWithPolarContext>, Void>, Serializable {
private static final long serialVersionUID = 3059764273262382648L;
private static final Logger logger = Logger.getLogger(CubicRegressionPerCourseProcessor.class.getName());
private final Map<GroupKey, AngleAndSpeedRegression> regressions = new HashMap<>();
/**
* FIXME Make sure replication and listeners interact correctly
*/
private transient ConcurrentMap<BoatClass, Set<PolarsChangedListener>> listeners;
@Override
public boolean canProcessElements() {
// TODO Auto-generated method stub
return true;
}
@Override
public void processElement(GroupedDataEntry<GPSFixMovingWithPolarContext> element) {
GPSFixMovingWithPolarContext fix = element.getDataEntry();
if (fix.getLegType() == LegType.UPWIND || fix.getLegType() == LegType.DOWNWIND) {
GroupKey key = element.getKey();
AngleAndSpeedRegression regression;
synchronized (regressions) {
regression = regressions.get(key);
if (regression == null) {
regression = new AngleAndSpeedRegression();
regressions.put(key, regression);
}
}
regression.addData(fix.getWind(), fix.getAbsoluteAngleToTheWind(), fix.getBoatSpeed());
Set<PolarsChangedListener> listenersForBoatClass = listeners.get(fix.getBoatClass());
if (listenersForBoatClass != null) {
for (PolarsChangedListener listener : listenersForBoatClass) {
listener.polarsChanged();
}
}
}
}
/**
* Returns speed and twa for a given scenario, if data is available in the regression.
*/
public SpeedWithBearingWithConfidence<Void> getAverageSpeedAndCourseOverGround(BoatClass boatClass,
Speed windSpeed, LegType legType) throws NotEnoughDataHasBeenAddedException {
GroupKey key = createGroupKey(boatClass, legType);
SpeedWithBearingWithConfidence<Void> estimatedSpeedAndAngle = null;
if (regressions.containsKey(key)) {
estimatedSpeedAndAngle = regressions.get(key).estimateSpeedAndAngle(windSpeed);
} else {
throw new NotEnoughDataHasBeenAddedException("Not enough data has been added to Per Course Regressions");
}
return estimatedSpeedAndAngle;
}
/**
* Returns windspeed and windangle candidates for a given scenario, if enough data is available.
*/
public Set<SpeedWithBearingWithConfidence<Void>> estimateTrueWindSpeedAndAngleCandidates(BoatClass boatClass,
Speed speedOverGround, LegType legType, Tack tack) {
GroupKey key = createGroupKey(boatClass, legType);
Set<SpeedWithBearingWithConfidence<Void>> result = new HashSet<>();
if (regressions.containsKey(key)) {
try {
result = regressions.get(key).estimateTrueWindSpeedAndAngleCandidates(speedOverGround, legType, tack);
} catch (NotEnoughDataHasBeenAddedException e) {
// Return empty result
}
} else {
// Return empty result;
}
return result;
}
private GroupKey createGroupKey(final BoatClass boatClass, final LegType legType) {
LegTypePolarClusterKey key = new LegTypePolarClusterKey() {
@Override
public BoatClass getBoatClass() {
return boatClass;
}
@Override
public LegType getLegType() {
return legType;
}
};
GroupKey compoundKey;
try {
compoundKey = GroupKeyFactory.createNestingCompoundKeyFor(key, PolarDataDimensionCollectionFactory
.getCubicRegressionPerCourseClusterKeyDimensions());
} catch (NoSuchMethodException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
return compoundKey;
}
/**
* Allows direct access to speed regression functions for debugging purposes.
*
* For actual usage of the data please use
* {@link #getAverageSpeedAndCourseOverGround(BoatClass, Speed, LegType)}
* and
* {@link #estimateTrueWindSpeedAndAngleCandidates(BoatClass, Speed, LegType, Tack)}.
*/
public PolynomialFunction getSpeedRegressionFunction(BoatClass boatClass, LegType legType)
throws NotEnoughDataHasBeenAddedException {
GroupKey key = createGroupKey(boatClass, legType);
PolynomialFunction polynomialFunction;
if (regressions.containsKey(key)) {
polynomialFunction = regressions.get(key).getSpeedRegressionFunction();
} else {
throw new NotEnoughDataHasBeenAddedException();
}
return polynomialFunction;
}
/**
* Allows direct access to angle regression functions for debugging purposes.
*
* For actual usage of the data please use
* {@link #getAverageSpeedAndCourseOverGround(BoatClass, Speed, LegType)}
* and
* {@link #estimateTrueWindSpeedAndAngleCandidates(BoatClass, Speed, LegType, Tack)}.
*/
public PolynomialFunction getAngleRegressionFunction(BoatClass boatClass, LegType legType)
throws NotEnoughDataHasBeenAddedException {
GroupKey key = createGroupKey(boatClass, legType);
PolynomialFunction polynomialFunction;
if (regressions.containsKey(key)) {
polynomialFunction = regressions.get(key).getAngleRegressionFunction();
} else {
throw new NotEnoughDataHasBeenAddedException();
}
return polynomialFunction;
}
@Override
public void onFailure(Throwable failure) {
failure.printStackTrace();
logger.severe("Polar Data Mining Pipe failed. Cause: " + failure.getMessage());
throw new RuntimeException("Polar Data Miner failed.", failure);
}
public void setListeners(ConcurrentMap<BoatClass, Set<PolarsChangedListener>> listeners) {
this.listeners = listeners;
}
@Override
public Class<GroupedDataEntry<GPSFixMovingWithPolarContext>> getInputType() {
// TODO Auto-generated method stub
return null;
}
@Override
public Class<Void> getResultType() {
// No result type here, since this is a special case of a processor. It's the end of the pipe so to say.
return null;
}
@Override
public void finish() throws InterruptedException {
// Nothing to do here
}
@Override
public boolean isFinished() {
return false;
}
@Override
public void abort() {
// TODO Auto-generated method stub
}
@Override
public boolean isAborted() {
// TODO Auto-generated method stub
return false;
}
@Override
public AdditionalResultDataBuilder getAdditionalResultData(AdditionalResultDataBuilder additionalDataBuilder) {
// TODO Auto-generated method stub
return null;
}
public Map<GroupKey, AngleAndSpeedRegression> getRegressions() {
synchronized (regressions) {
return Collections.unmodifiableMap(regressions);
}
}
public void updateRegressions(Map<GroupKey, AngleAndSpeedRegression> regressionsToUpdate) {
synchronized (regressions) {
regressions.putAll(regressionsToUpdate);
}
}
}
@@ -1,236 +1,246 @@
package com.sap.sailing.polars.mining;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger;
import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.SpeedWithConfidence;
import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.domain.polars.PolarsChangedListener;
import com.sap.sailing.polars.regression.IncrementalLeastSquares;
import com.sap.sailing.polars.regression.impl.IncrementalAnyOrderLeastSquaresImpl;
import com.sap.sse.datamining.components.AdditionalResultDataBuilder;
import com.sap.sse.datamining.components.Processor;
import com.sap.sse.datamining.data.Cluster;
import com.sap.sse.datamining.data.ClusterGroup;
import com.sap.sse.datamining.factories.GroupKeyFactory;
import com.sap.sse.datamining.impl.components.GroupedDataEntry;
import com.sap.sse.datamining.shared.GroupKey;
/**
* Holds one speed regression per BoatClass, WindSpeed, Beat Angle Range combination and provides means for adding and
* accessing data. Data is added incrementally in and not all historic fixes need to be maintained. This allows for a
* very space efficient structure.
*
* @author D054528 (Frederik Petersen)
*
*/
public class SpeedRegressionPerAngleClusterProcessor implements
Processor<GroupedDataEntry<GPSFixMovingWithPolarContext>, Void>, Serializable {
private static final long serialVersionUID = 3279917556091599077L;
private static final Logger logger = Logger.getLogger(CubicRegressionPerCourseProcessor.class.getName());
private final Map<GroupKey, IncrementalLeastSquares> regressions = new HashMap<>();
private final Map<BoatClass, Long> fixCountPerBoatClass = new HashMap<>();
private final ClusterGroup<Bearing> angleClusterGroup;
/**
* FIXME make sure listeners and replication interact correctly
*/
private transient ConcurrentMap<BoatClass, Set<PolarsChangedListener>> listeners;
private final Set<BoatClass> availableBoatClasses = new HashSet<BoatClass>();
public SpeedRegressionPerAngleClusterProcessor(ClusterGroup<Bearing> angleClusterGroup) {
this.angleClusterGroup = angleClusterGroup;
}
@Override
public boolean canProcessElements() {
// TODO Auto-generated method stub
return true;
}
@Override
public void processElement(GroupedDataEntry<GPSFixMovingWithPolarContext> element) {
GroupKey key = element.getKey();
IncrementalLeastSquares regression;
BoatClass boatClass = element.getDataEntry().getBoatClass();
synchronized (fixCountPerBoatClass) {
if (!fixCountPerBoatClass.containsKey(boatClass)) {
fixCountPerBoatClass.put(boatClass, 0L);
}
fixCountPerBoatClass.put(boatClass, fixCountPerBoatClass.get(boatClass) + 1);
}
synchronized (regressions) {
regression = regressions.get(key);
if (regression == null) {
regression = new IncrementalAnyOrderLeastSquaresImpl(3, false);
regressions.put(key, regression);
}
}
GPSFixMovingWithPolarContext fix = element.getDataEntry();
regression.addData(fix.getWind().getObject().getKnots(), fix.getBoatSpeed().getObject().getKnots());
availableBoatClasses.add(boatClass);
Set<PolarsChangedListener> listenersForBoatClass = listeners.get(fix.getBoatClass());
if (listenersForBoatClass != null) {
for (PolarsChangedListener listener : listenersForBoatClass) {
listener.polarsChanged();
}
}
}
/**
* There are angle clusters (size defined in the data mining pipeline construction), which each have their own
* regression for boatspeed over windspeed. We don't know the thresholds or centers of the angle clusters here, so
* we roughly interpolate by taking 10 values from angle-5 deg to angle+5 deg and average the speeds.
*
* At the time of writing the size of each angle range is 5° so this method provides a pretty smooth interpolation.
*/
public SpeedWithConfidence<Void> estimateBoatSpeed(BoatClass boatClass, Speed windSpeed, Bearing trueWindAngle)
throws NotEnoughDataHasBeenAddedException {
double speedSum = 0;
double numberOfSpeeds = 0;
long fixCount = 0;
for (int i = -2; i <= 2; i++) {
GroupKey key = createGroupKey(boatClass, new DegreeBearingImpl(Math.abs(trueWindAngle.getDegrees()) + i));
if (regressions.containsKey(key)) {
IncrementalLeastSquares incrementalLeastSquares = regressions.get(key);
fixCount = fixCount + incrementalLeastSquares.getNumberOfAddedPoints();
if (fixCount > 10) {
speedSum += incrementalLeastSquares.getOrCreatePolynomialFunction().value(windSpeed.getKnots());
numberOfSpeeds++;
}
}
}
fixCount = (long) (fixCount / numberOfSpeeds);
long fixCountOverall = 0;
if (numberOfSpeeds < 2 || fixCount < 10) {
throw new NotEnoughDataHasBeenAddedException("Not enough data has been added to Per Course Regressions");
} else {
synchronized (fixCountPerBoatClass) {
fixCountOverall = fixCountPerBoatClass.get(boatClass);
}
}
Speed speed = new KnotSpeedImpl(speedSum / numberOfSpeeds);
return new SpeedWithConfidenceImpl<Void>(speed, Math.min(1, 5.0 * ((double) fixCount / fixCountOverall)), null);
}
private GroupKey createGroupKey(final BoatClass boatClass, final Bearing angle) {
AngleClusterPolarClusterKey key = new AngleClusterPolarClusterKey() {
@Override
public BoatClass getBoatClass() {
return boatClass;
}
@Override
public Cluster<Bearing> getAngleCluster() {
return angleClusterGroup.getClusterFor(angle);
}
};
GroupKey compoundKey;
try {
compoundKey = GroupKeyFactory.createNestingCompoundKeyFor(key, PolarDataDimensionCollectionFactory
.getSpeedRegressionPerAngleClusterClusterKeyDimensions());
} catch (NoSuchMethodException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
return compoundKey;
}
@Override
public void onFailure(Throwable failure) {
logger.severe("Polar Data Mining Pipe failed.");
throw new RuntimeException("Polar Data Miner failed.", failure);
}
/**
* Allows direct access to angle regression functions for debugging purposes.
*
* For actual usage of the data please use
* {@link #estimateBoatSpeed(BoatClass, Speed, Bearing)}.
*/
public PolynomialFunction getSpeedRegressionFunction(BoatClass boatClass, double trueWindAngle)
throws NotEnoughDataHasBeenAddedException {
GroupKey key = createGroupKey(boatClass, new DegreeBearingImpl(trueWindAngle));
PolynomialFunction polynomialFunction;
if (regressions.containsKey(key)) {
polynomialFunction = regressions.get(key).getOrCreatePolynomialFunction();
} else {
throw new NotEnoughDataHasBeenAddedException();
}
return polynomialFunction;
}
public void setListeners(ConcurrentMap<BoatClass, Set<PolarsChangedListener>> listeners) {
this.listeners = listeners;
}
Set<BoatClass> getAvailableBoatClasses() {
return availableBoatClasses;
}
@Override
public Class<GroupedDataEntry<GPSFixMovingWithPolarContext>> getInputType() {
// TODO Auto-generated method stub
return null;
}
@Override
public Class<Void> getResultType() {
// No result type here, since this is a special case of a processor. It's the end of the pipe so to say.
return null;
}
@Override
public void finish() throws InterruptedException {
// Nothing to do here
}
@Override
public boolean isFinished() {
return false;
}
@Override
public void abort() {
// TODO Auto-generated method stub
}
@Override
public boolean isAborted() {
// TODO Auto-generated method stub
return false;
}
@Override
public AdditionalResultDataBuilder getAdditionalResultData(AdditionalResultDataBuilder additionalDataBuilder) {
// TODO Auto-generated method stub
return null;
}
public ClusterGroup<Bearing> getAngleCluster() {
return angleClusterGroup;
}
}
package com.sap.sailing.polars.mining;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger;
import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.SpeedWithConfidence;
import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.domain.polars.PolarsChangedListener;
import com.sap.sailing.polars.regression.IncrementalLeastSquares;
import com.sap.sailing.polars.regression.impl.IncrementalAnyOrderLeastSquaresImpl;
import com.sap.sse.datamining.components.AdditionalResultDataBuilder;
import com.sap.sse.datamining.components.Processor;
import com.sap.sse.datamining.data.Cluster;
import com.sap.sse.datamining.data.ClusterGroup;
import com.sap.sse.datamining.factories.GroupKeyFactory;
import com.sap.sse.datamining.impl.components.GroupedDataEntry;
import com.sap.sse.datamining.shared.GroupKey;
/**
* Holds one speed regression per BoatClass, WindSpeed, Beat Angle Range combination and provides means for adding and
* accessing data. Data is added incrementally in and not all historic fixes need to be maintained. This allows for a
* very space efficient structure.
*
* @author D054528 (Frederik Petersen)
*
*/
public class SpeedRegressionPerAngleClusterProcessor implements
Processor<GroupedDataEntry<GPSFixMovingWithPolarContext>, Void>, Serializable {
private static final long serialVersionUID = 3279917556091599077L;
private static final Logger logger = Logger.getLogger(CubicRegressionPerCourseProcessor.class.getName());
private final Map<GroupKey, IncrementalLeastSquares> regressions = new HashMap<>();
private final Map<BoatClass, Long> fixCountPerBoatClass = new HashMap<>();
private final ClusterGroup<Bearing> angleClusterGroup;
/**
* FIXME make sure listeners and replication interact correctly
*/
private transient ConcurrentMap<BoatClass, Set<PolarsChangedListener>> listeners;
public SpeedRegressionPerAngleClusterProcessor(ClusterGroup<Bearing> angleClusterGroup) {
this.angleClusterGroup = angleClusterGroup;
}
@Override
public boolean canProcessElements() {
// TODO Auto-generated method stub
return true;
}
@Override
public void processElement(GroupedDataEntry<GPSFixMovingWithPolarContext> element) {
GroupKey key = element.getKey();
IncrementalLeastSquares regression;
BoatClass boatClass = element.getDataEntry().getBoatClass();
synchronized (fixCountPerBoatClass) {
if (!fixCountPerBoatClass.containsKey(boatClass)) {
fixCountPerBoatClass.put(boatClass, 0L);
}
fixCountPerBoatClass.put(boatClass, fixCountPerBoatClass.get(boatClass) + 1);
}
synchronized (regressions) {
regression = regressions.get(key);
if (regression == null) {
regression = new IncrementalAnyOrderLeastSquaresImpl(3, false);
regressions.put(key, regression);
}
}
GPSFixMovingWithPolarContext fix = element.getDataEntry();
regression.addData(fix.getWind().getObject().getKnots(), fix.getBoatSpeed().getObject().getKnots());
Set<PolarsChangedListener> listenersForBoatClass = listeners.get(fix.getBoatClass());
if (listenersForBoatClass != null) {
for (PolarsChangedListener listener : listenersForBoatClass) {
listener.polarsChanged();
}
}
}
/**
* There are angle clusters (size defined in the data mining pipeline construction), which each have their own
* regression for boatspeed over windspeed. We don't know the thresholds or centers of the angle clusters here, so
* we roughly interpolate by taking 10 values from angle-5 deg to angle+5 deg and average the speeds.
*
* At the time of writing the size of each angle range is 5° so this method provides a pretty smooth interpolation.
*/
public SpeedWithConfidence<Void> estimateBoatSpeed(BoatClass boatClass, Speed windSpeed, Bearing trueWindAngle)
throws NotEnoughDataHasBeenAddedException {
double speedSum = 0;
double numberOfSpeeds = 0;
long fixCount = 0;
for (int i = -2; i <= 2; i++) {
GroupKey key = createGroupKey(boatClass, new DegreeBearingImpl(Math.abs(trueWindAngle.getDegrees()) + i));
if (regressions.containsKey(key)) {
IncrementalLeastSquares incrementalLeastSquares = regressions.get(key);
fixCount = fixCount + incrementalLeastSquares.getNumberOfAddedPoints();
if (fixCount > 10) {
speedSum += incrementalLeastSquares.getOrCreatePolynomialFunction().value(windSpeed.getKnots());
numberOfSpeeds++;
}
}
}
fixCount = (long) (fixCount / numberOfSpeeds);
long fixCountOverall = 0;
if (numberOfSpeeds < 2 || fixCount < 10) {
throw new NotEnoughDataHasBeenAddedException("Not enough data has been added to Per Course Regressions");
} else {
synchronized (fixCountPerBoatClass) {
fixCountOverall = fixCountPerBoatClass.get(boatClass);
}
}
Speed speed = new KnotSpeedImpl(speedSum / numberOfSpeeds);
return new SpeedWithConfidenceImpl<Void>(speed, Math.min(1, 5.0 * ((double) fixCount / fixCountOverall)), null);
}
private GroupKey createGroupKey(final BoatClass boatClass, final Bearing angle) {
AngleClusterPolarClusterKey key = new AngleClusterPolarClusterKey() {
@Override
public BoatClass getBoatClass() {
return boatClass;
}
@Override
public Cluster<Bearing> getAngleCluster() {
return angleClusterGroup.getClusterFor(angle);
}
};
GroupKey compoundKey;
try {
compoundKey = GroupKeyFactory.createNestingCompoundKeyFor(key, PolarDataDimensionCollectionFactory
.getSpeedRegressionPerAngleClusterClusterKeyDimensions());
} catch (NoSuchMethodException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
return compoundKey;
}
@Override
public void onFailure(Throwable failure) {
logger.severe("Polar Data Mining Pipe failed.");
throw new RuntimeException("Polar Data Miner failed.", failure);
}
/**
* Allows direct access to angle regression functions for debugging purposes.
*
* For actual usage of the data please use
* {@link #estimateBoatSpeed(BoatClass, Speed, Bearing)}.
*/
public PolynomialFunction getSpeedRegressionFunction(BoatClass boatClass, double trueWindAngle)
throws NotEnoughDataHasBeenAddedException {
GroupKey key = createGroupKey(boatClass, new DegreeBearingImpl(trueWindAngle));
PolynomialFunction polynomialFunction;
if (regressions.containsKey(key)) {
polynomialFunction = regressions.get(key).getOrCreatePolynomialFunction();
} else {
throw new NotEnoughDataHasBeenAddedException();
}
return polynomialFunction;
}
public void setListeners(ConcurrentMap<BoatClass, Set<PolarsChangedListener>> listeners) {
this.listeners = listeners;
}
Set<BoatClass> getAvailableBoatClasses() {
return Collections.unmodifiableSet(fixCountPerBoatClass.keySet());
}
@Override
public Class<GroupedDataEntry<GPSFixMovingWithPolarContext>> getInputType() {
// TODO Auto-generated method stub
return null;
}
@Override
public Class<Void> getResultType() {
// No result type here, since this is a special case of a processor. It's the end of the pipe so to say.
return null;
}
@Override
public void finish() throws InterruptedException {
// Nothing to do here
}
@Override
public boolean isFinished() {
return false;
}
@Override
public void abort() {
// TODO Auto-generated method stub
}
@Override
public boolean isAborted() {
// TODO Auto-generated method stub
return false;
}
@Override
public AdditionalResultDataBuilder getAdditionalResultData(AdditionalResultDataBuilder additionalDataBuilder) {
// TODO Auto-generated method stub
return null;
}
public ClusterGroup<Bearing> getAngleCluster() {
return angleClusterGroup;
}
public Map<GroupKey, IncrementalAnyOrderLeastSquaresImpl> getRegressionsImpl() {
synchronized (regressions) {
Map<GroupKey, IncrementalAnyOrderLeastSquaresImpl> map = new HashMap<>();
regressions.keySet().stream().forEach(key -> map.put(key, (IncrementalAnyOrderLeastSquaresImpl) regressions.get(key)));
return Collections.unmodifiableMap(map);
}
}
public Map<BoatClass, Long> getFixCountPerBoatClass() {
synchronized (fixCountPerBoatClass) {
return Collections.unmodifiableMap(fixCountPerBoatClass);
}
}
}
@@ -82,6 +82,17 @@ public class IncrementalAnyOrderLeastSquaresImpl implements IncrementalLeastSqua
private final boolean hasIntercept;
private final boolean useSymbollicInversionIfPossible;
public IncrementalAnyOrderLeastSquaresImpl(double[][] matrixOfXSums, double[] vectorOfXYMultSums, int polynomialOrder, boolean hasIntercept,
boolean useSymbollicInversionIfPossible, long numberOfPointsAdded) {
this.hasIntercept = hasIntercept;
this.polynomialOrder = polynomialOrder;
this.useSymbollicInversionIfPossible = useSymbollicInversionIfPossible;
this.matrixOfXSums = matrixOfXSums;
this.vectorOfXYMultSums = vectorOfXYMultSums;
this.numberOfPointsAdded.set(numberOfPointsAdded);
functionNeedsUpdate.set(true);
}
public IncrementalAnyOrderLeastSquaresImpl(int polynomialOrder, boolean hasIntercept,
boolean useSymbollicInversionIfPossible) {
@@ -321,4 +332,24 @@ public class IncrementalAnyOrderLeastSquaresImpl implements IncrementalLeastSqua
return numberOfPointsAdded.get();
}
public double[][] getMatrixOfXSums() {
return matrixOfXSums;
}
public double[] getVectorOfXYMultSums() {
return vectorOfXYMultSums;
}
public int getPolynomialOrder() {
return polynomialOrder;
}
public boolean isHasIntercept() {
return hasIntercept;
}
public boolean isUseSymbollicInversionIfPossible() {
return useSymbollicInversionIfPossible;
}
}
@@ -263,7 +263,8 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
private void writeCommonLeaderboardData(JSONObject jsonLeaderboard, LeaderboardDTO leaderboard,
ResultStates resultState, TimePoint resultTimePoint, Integer maxCompetitorsCount) {
jsonLeaderboard.put("name", leaderboard.name);
final String displayName = leaderboard.getDisplayName();
jsonLeaderboard.put("displayName", displayName == null ? leaderboard.name : displayName);
jsonLeaderboard.put("resultTimepoint", resultTimePoint != null ? resultTimePoint.asMillis() : null);
jsonLeaderboard.put("resultState", resultState.name());
jsonLeaderboard.put("maxCompetitorsCount", maxCompetitorsCount);
@@ -282,7 +283,8 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
private void writeCommonLeaderboardData(JSONObject jsonLeaderboard, Leaderboard leaderboard,
ResultStates resultState, TimePoint resultTimePoint, Integer maxCompetitorsCount) {
jsonLeaderboard.put("name", leaderboard.getName());
final String displayName = leaderboard.getDisplayName();
jsonLeaderboard.put("displayName", displayName == null ? leaderboard.getName() : displayName);
jsonLeaderboard.put("resultTimepoint", resultTimePoint != null ? resultTimePoint.asMillis() : null);
jsonLeaderboard.put("resultState", resultState.name());
jsonLeaderboard.put("maxCompetitorsCount", maxCompetitorsCount);
@@ -22,7 +22,7 @@
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console -clean"/>
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dfile.encoding=cp1252 -Dexpedition.udp.port=2010 -Xmx6000m -XX:+UseG1GC -Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080 -Dhttps.proxyHost=proxy -Dhttps.proxyPort=8080 -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -Dkiwo.results=${project_loc:com.sap.sailing.kiworesultimport.test}/resources -Dpersistentcompetitors.clear=false -XX:+UseMembar -Digtimi.client.id=7fcdd217e0aa16090edb4ad55b09ec43b2021090e209541fc9b7003c2a2b70c6 -Digtimi.client.secret=aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a -Digtimi.client.redirecturi=http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dfile.encoding=cp1252 -Dexpedition.udp.port=2010 -Xmx6000m -XX:+UseG1GC -Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080 -Dhttps.proxyHost=proxy -Dhttps.proxyPort=8080 -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -Dkiwo.results=${project_loc:com.sap.sailing.kiworesultimport.test}/resources -Dpersistentcompetitors.clear=false -XX:+UseMembar -Digtimi.client.id=7fcdd217e0aa16090edb4ad55b09ec43b2021090e209541fc9b7003c2a2b70c6 -Digtimi.client.secret=aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a -Digtimi.client.redirecturi=http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback -Dpolardata.source.url=https://www.sapsailing.com"/>
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc}"/>
<stringAttribute key="pde.version" value="3.3"/>
<stringAttribute key="profilingTraceType-ALLOCATION_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_INC_LINE_NRS%CTX_KEY%true%CTX_ENTRY%KEY_MAX_SIZE%CTX_KEY%65536%CTX_ENTRY%INCREASE_COUNT%CTX_KEY%8192%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ID%CTX_KEY%false%CTX_ENTRY%KEY_MIN_SIZE%CTX_KEY%32%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%CLASS_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ADAPTIVE%CTX_KEY%false%CTX_ENTRY%KEY_JRUBY_SUPPORT%CTX_KEY%false%CTX_ENTRY%"/>
@@ -23,6 +23,18 @@
<h2 class="releaseHeadline">Release Notes - Administration Console</h2>
<div class="innerContent">
<h2 class="articleSubheadline">November 2016</h2>
<ul class="bulletList">
<li>When launching a server, a system property <tt>polardata.source.url</tt> can
be provided in order to specify a server base URL from where to import polar
data into the upstarting server. Usually, this would be the "archive" server's
base URL, such as https://www.sapsailing.com. Note that the exporting server
needs to already support this feature and the importing and exporting server
should be at roughly the same version to ensure binary compatibility for the
polar sheet data.
</li>
</ul>
<h2 class="articleSubheadline">October 2016</h2>
<ul class="bulletList">
<li>A regatta now has an additional property: "Buoy zone radius in hull lengths." It
@@ -11,8 +11,8 @@ public class CompoundGroupKey extends AbstractGroupKey {
private static final long serialVersionUID = -7902450253393172550L;
private List<? extends GroupKey> keys;
private boolean hasSubKeys;
private List<? extends GroupKey> keys; // non-final only due to GWT serialization; custom field serializer would change this
private boolean hasSubKeys; // non-final only due to GWT serialization; custom field serializer would change this
/**
* Constructor for the GWT-Serialization. Don't use this!
@@ -24,7 +24,6 @@ public class CompoundGroupKey extends AbstractGroupKey {
if (keys == null || keys.isEmpty()) {
throw new IllegalArgumentException("The keys mustn't be null or empty");
}
this.keys = new ArrayList<>(keys);
hasSubKeys = this.keys.size() > 1;
}
@@ -0,0 +1,286 @@
package com.sap.sse.gwt.client.panels;
import java.util.ArrayList;
import java.util.List;
import com.google.gwt.animation.client.Animation;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
/**
* This scrollpanel overlays a second scrollbar fixed to the bottom of the viewport as soon as the original scrollbar is
* not visible anymore.<p>
*
* This class observes a set of events to properly synchronize scrollbar states:
*
* <ul>
* <li>scrollevents on both scrollbars</li>
* <li>window resize events</li>
* <li>DOM mutation events on the scrollpanel child widget</li>
* </ul>
*/
public class OverlayAssistantScrollPanel extends ScrollPanel {
private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class);
interface MyUiBinder extends UiBinder<Widget, OverlayAssistantScrollPanel> {
}
@UiField
protected ScrollPanel overlayScrollPanelUi;
@UiField
protected SimplePanel overlayDummyContentUi;
private final OverlayAnimation animation = new OverlayAnimation();
private final List<HandlerRegistration> registrations = new ArrayList<>();
private Widget overlayWidget;
private final Element contentToSyncWith;
private final boolean hasMutationObservationCapability;
/**
* Create an overlay scroll panel with the corresponding widget to scroll.
*
* @param contentToScroll
*/
public OverlayAssistantScrollPanel(Widget contentToScroll) {
super(contentToScroll);
// add 15 pixel spacing between bottom scrollpanel and scrolled content
this.getElement().getStyle().setPaddingBottom(15, Unit.PX);
contentToSyncWith = contentToScroll.getElement();
this.hasMutationObservationCapability = weCanObserve();
}
/**
* Bind all observers required to manage both scrollbars in sync.
*/
@Override
protected void onAttach() {
super.onAttach();
GWT.log("onAttach");
if (!hasMutationObservationCapability) {
return;
}
overlayWidget = uiBinder.createAndBindUi(this);
RootPanel.get().add(overlayWidget);
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
matchScrollpanelAndContentWidths();
updateOverlayDisplay();
syncScrollers(OverlayAssistantScrollPanel.this, overlayScrollPanelUi);
}
});
registrations.add(Window.addResizeHandler(new ResizeHandler() {
@Override
public void onResize(ResizeEvent event) {
matchScrollpanelAndContentWidths();
updateOverlayDisplay();
syncScrollers(OverlayAssistantScrollPanel.this, overlayScrollPanelUi);
}
}));
registrations.add(Window.addWindowScrollHandler(new Window.ScrollHandler() {
@Override
public void onWindowScroll(com.google.gwt.user.client.Window.ScrollEvent event) {
matchScrollpanelAndContentWidths();
updateOverlayDisplay();
}
}));
registrations.add(this.addScrollHandler(new ScrollHandler() {
@Override
public void onScroll(ScrollEvent event) {
syncScrollers(OverlayAssistantScrollPanel.this, overlayScrollPanelUi);
}
}));
registrations.add(overlayScrollPanelUi.addScrollHandler(new ScrollHandler() {
@Override
public void onScroll(ScrollEvent event) {
syncScrollers(overlayScrollPanelUi, OverlayAssistantScrollPanel.this);
}
}));
final JavaScriptObject observer = setupObserver(contentToSyncWith, new Command() {
@Override
public void execute() {
GWT.log("contentToSyncWith");
matchScrollpanelAndContentWidths();
updateOverlayDisplay();
syncScrollers(OverlayAssistantScrollPanel.this, overlayScrollPanelUi);
}
});
registrations.add(new HandlerRegistration() {
@Override
public void removeHandler() {
if (observer != null) {
disconnectObserver(observer);
}
}
});
}
/**
* Detach all observers.
*/
@Override
protected void onDetach() {
GWT.log("onDetach");
super.onDetach();
if (!hasMutationObservationCapability) {
return;
}
animation.detach();
for (HandlerRegistration handlerRegistration : registrations) {
handlerRegistration.removeHandler();
}
if (overlayWidget.isAttached()) {
overlayWidget.removeFromParent();
}
}
/**
* Helper method that synchronizes the scroll position of the target scrollpanel, using the data gathered from the
* source scrollpanel
*/
private void syncScrollers(ScrollPanel source, ScrollPanel target) {
target.setHorizontalScrollPosition(source.getHorizontalScrollPosition());
}
/**
* Helper method that synchronizes widget sizes of overlay elements with the size of the real scrollpanel and
* content.
*/
private void matchScrollpanelAndContentWidths() {
overlayDummyContentUi.getElement().getStyle().setWidth(contentToSyncWith.getClientWidth(), Unit.PX);
overlayScrollPanelUi.getElement().getStyle().setWidth(OverlayAssistantScrollPanel.this.getOffsetWidth() + 16,
Unit.PX);
overlayScrollPanelUi.getElement().getStyle().setMarginLeft(this.getAbsoluteLeft(), Unit.PX);
}
/**
* This methods triggers the animation that shows/ hides the overlay scrollbar.
*/
private void updateOverlayDisplay() {
// docViewTop and docViewBottom contain the window viewport size
int docViewTop = Window.getScrollTop();
int docViewBottom = docViewTop + Window.getClientHeight();
// elemTop and elemBottom contain the absolute position of the original viewport
int elemTop = this.getAbsoluteTop();
int elemBottom = elemTop + this.getOffsetHeight() + 15;
// are we scrolling?
boolean scrollbarIsShown = isElementHorizontalScrollShown();
boolean syncedScrollbarIsVisible = (elemBottom <= docViewBottom);
boolean scrollPanelEntirelyOutOfView = (elemTop + 40) > overlayWidget.getAbsoluteTop();
animation.updateOverlayToState(scrollbarIsShown && !syncedScrollbarIsVisible && !scrollPanelEntirelyOutOfView);
}
/**
* Checks if the content in the scrollpanel fits in the scrollpanel without scrolling
*/
public boolean isElementHorizontalScrollShown() {
return contentToSyncWith.getClientWidth() > this.getElement().getClientWidth();
}
/**
* Uses JSNI to setup a mutation observer. This way we get notified when the content inside the scrollpanel changes
* on dom-level. As soon as we have changes, we execute the command provided by this method.
*/
private native JavaScriptObject setupObserver(final Element elementToObserve, Command onChangeCommand) /*-{
if (MutationObserver) {
var observer = new MutationObserver(
function(mutations) {
onChangeCommand.@com.google.gwt.user.client.Command::execute()();
});
var observerConfig = {
attributes : true,
childList : true,
characterData : true
}
observer.observe(elementToObserve, observerConfig);
return observer;
} else {
return null;
}
}-*/;
/**
* Disconnect the mutation observer.
*/
private native void disconnectObserver(final JavaScriptObject observer) /*-{
if (observer) {
observer.disconnect();
}
}-*/;
/**
* Check if the browser provides the mutation observer API.
*/
private native boolean weCanObserve() /*-{
return (MutationObserver);
}-*/;
/**
* Wrapper class the effectively does show/ hide the overlay scrollbar.
*/
private final class OverlayAnimation extends Animation {
private boolean overlayToolbarIsCurrentlyVisible = false;
private boolean overlayRequestedTargetState;
/**
* Convenience method to trigger animation.
*/
public void updateOverlayToState(boolean doShowOverlay) {
animate(doShowOverlay, 200);
}
/**
* Convenience method to reset state to "not visible", used when overlay gets detached from view.
*/
public void detach() {
overlayToolbarIsCurrentlyVisible = false;
}
/**
* Trigger overlay display animation The animation only fires if the current internal state and the target state
* are differ.
*/
public void animate(boolean requestedVisibleState, int duration) {
boolean changed = overlayToolbarIsCurrentlyVisible != requestedVisibleState;
if (!changed)
return;
overlayRequestedTargetState = requestedVisibleState;
run(duration);
}
@Override
protected void onStart() {
overlayWidget.getElement().getStyle().setOpacity(overlayRequestedTargetState ? 0 : 1);
}
@Override
protected void onUpdate(double progress) {
overlayWidget.getElement().getStyle().setOpacity(overlayRequestedTargetState ? progress : 1 - progress);
}
protected void onComplete() {
overlayToolbarIsCurrentlyVisible = overlayRequestedTargetState;
overlayWidget.getElement().getStyle().setOpacity(overlayRequestedTargetState ? 1 : 0);
};
protected void onCancel() {
onComplete();
}
};
}
@@ -0,0 +1,32 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:style>
.outerBox {
overflow: hidden;
position: fixed;
left: 0px;
right: 0px;
bottom: 0px;
background-color: white;
height: 40px;
opacity: 0;
}
.scrollPanel {
height: 20px;
padding-top:25px;
}
.content {
height: 1px;
}
</ui:style>
<g:SimplePanel styleName="{style.outerBox}">
<g:SimplePanel>
<g:ScrollPanel ui:field="overlayScrollPanelUi"
styleName="{style.scrollPanel}">
<g:SimplePanel ui:field="overlayDummyContentUi"
styleName="{style.content}" />
</g:ScrollPanel>
</g:SimplePanel>
</g:SimplePanel>
</ui:UiBinder>
@@ -25,7 +25,7 @@ import com.google.gwt.user.client.ui.Widget;
*/
public class SAPHeader extends Composite {
private static final String LOGO_URL = "https://sapsponsorships.com/";
private static final String LOGO_URL = "https://www.sap.com/sponsorships";
private static SAPHeaderUiBinder uiBinder = GWT.create(SAPHeaderUiBinder.class);
@@ -13,7 +13,7 @@ public class NewAccountValidator {
private static final int MINIMUM_USERNAME_LENGTH = 3;
private static final int MINIMUM_PASSWORD_LENGTH = 5;
private final StringMessages stringMessages;
protected final StringMessages stringMessages;
public NewAccountValidator(com.sap.sse.security.ui.client.i18n.StringMessages stringMessages) {
this.stringMessages = stringMessages;
@@ -43,7 +43,7 @@ public class NewAccountValidator {
return result;
}
private String validateUsername(String username) {
protected String validateUsername(String username) {
final String result;
if (username == null || username.length() < MINIMUM_USERNAME_LENGTH) {
result = stringMessages.usernameMustHaveAtLeastNCharacters(MINIMUM_USERNAME_LENGTH);
@@ -99,4 +99,5 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages {
String error();
String refresh();
String preferredLanguage();
String cannotResetInvalidURL();
}
@@ -95,3 +95,4 @@ accountConfirmation=Account confirmation
error=Unknown error
refresh=Refresh
preferredLanguage=Preferred language
cannotResetInvalidURL=Invalid reset URL...
@@ -95,3 +95,4 @@ accountConfirmation=Kontobestätigung
error=Es ist ein Fehler aufgetreten
refresh=Aktualisieren
preferredLanguage=Bevorzugte Sprache
cannotResetInvalidURL="Fehlerhafte Reseturl ..."
@@ -341,19 +341,13 @@ public class HomeFragment extends AbstractHomeFragment implements LoaderCallback
NetworkHelperSuccessListener successListener = new NetworkHelperSuccessListener() {
@Override
public void performAction(JSONObject response) {
StartActivity startActivity = (StartActivity) getActivity();
startActivity.dismissProgressDialog();
DatabaseHelper.getInstance().deleteRegattaFromDatabase(getActivity(), checkinDigest);
reloadList();
dismissProgressDialogDeleteRegattaAndReloadList(checkinDigest);
}
};
NetworkHelperFailureListener failureListener = new NetworkHelperFailureListener() {
@Override
public void performAction(NetworkHelperError e) {
StartActivity startActivity = (StartActivity) getActivity();
startActivity.dismissProgressDialog();
startActivity.showErrorPopup(R.string.error,
R.string.error_could_not_complete_operation_on_server_try_again);
dismissProgressDialogDeleteRegattaAndReloadList(checkinDigest);
}
};
CheckoutHelper checkoutHelper = new CheckoutHelper();
@@ -370,6 +364,13 @@ public class HomeFragment extends AbstractHomeFragment implements LoaderCallback
}
}
public void dismissProgressDialogDeleteRegattaAndReloadList(final String checkinDigest) {
StartActivity startActivity = (StartActivity) getActivity();
startActivity.dismissProgressDialog();
DatabaseHelper.getInstance().deleteRegattaFromDatabase(getActivity(), checkinDigest);
reloadList();
}
private class ItemClickListener implements OnItemClickListener {
@Override
-120
View File
@@ -1,120 +0,0 @@
{
"comment":"https://wiki.wdf.sap.corp/wiki/display/SLS/Translation+Configuration+File+for+GIT+Workflow",
"collections":[
{
"collectionName":"DefaultGwtUi",
"folders":[
{
"sourceFolderPath":"java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client",
"sourceFilters":[
"StringMessages*.properties"
],
"targetFolderPath":".",
"pseudoLocFolderPath":"."
}
]
},
{
"collectionName":"RacelogTracking",
"folders":[
{
"sourceFolderPath":"java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages",
"sourceFilters":[
"TrackingStringMessages*.properties"
],
"xlfFolderPath":"./XLF",
"targetFolderPath":".",
"pseudoLocFolderPath":"."
}
]
},
{
"collectionName":"Polars",
"folders":[
{
"sourceFolderPath":"java/com.sap.sailing.polars.datamining/resources/stringmessages",
"sourceFilters":[
"Polars_StringMessages*.properties"
],
"xlfFolderPath":"./XLF",
"targetFolderPath":".",
"pseudoLocFolderPath":"."
}
]
},
{
"collectionName":"DataMining",
"folders":[
{
"sourceFolderPath":"java/com.sap.sse.datamining/resources/stringmessages",
"sourceFilters":[
"StringMessages*.properties"
],
"xlfFolderPath":"./XLF",
"targetFolderPath":".",
"pseudoLocFolderPath":"."
},
{
"sourceFolderPath":"java/com.sap.sailing.datamining/resources/stringmessages",
"sourceFilters":[
"Sailing_StringMessages*.properties"
],
"xlfFolderPath":"./XLF",
"targetFolderPath":".",
"pseudoLocFolderPath":"."
}
]
},
{
"collectionName":"FileStorage",
"folders":[
{
"sourceFolderPath":"java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage",
"sourceFilters":[
"StringMessages*.properties"
],
"xlfFolderPath":"./XLF",
"targetFolderPath":".",
"pseudoLocFolderPath":"."
},
{
"sourceFolderPath":"java/com.sap.sse.filestorage/resources/stringmessages",
"sourceFilters":[
"FileStorageStringMessages*.properties"
],
"xlfFolderPath":"./XLF",
"targetFolderPath":".",
"pseudoLocFolderPath":"."
}
]
},
{
"collectionName":"SecurityGwtUi",
"folders":[
{
"sourceFolderPath":"java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n",
"sourceFilters":[
"StringMessages*.properties"
],
"xlfFolderPath":"./XLF",
"targetFolderPath":".",
"pseudoLocFolderPath":"."
}
]
},
{
"collectionName":"SailingServer",
"folders":[
{
"sourceFolderPath":"java/com.sap.sailing.server/resources/stringmessages",
"sourceFilters":[
"StringMessages*.properties"
],
"xlfFolderPath":"./XLF",
"targetFolderPath":".",
"pseudoLocFolderPath":"."
}
]
}
]
}
+155
View File
@@ -0,0 +1,155 @@
{
"comment":"https://wiki.wdf.sap.corp/wiki/display/SLS/Translation+Configuration+File+for+GIT+Workflow",
"collections":[
{
"collectionName":"DefaultGwtUi",
"folders":[
{
"startingFolderPath":"java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client",
"sourceFilters":[
"**/StringMessages*.properties"
],
"excludedSourceFilters":[
"**/*_zh.properties"
],
"xlfFolderPath":"/XLF/[fullPath]/[targetFile]",
"targetFolderPath":".",
"pseudoLocFolderPath":"."
}
]
},
{
"collectionName":"RacelogTracking",
"folders":[
{
"startingFolderPath":"java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages",
"sourceFilters":[
"**/TrackingStringMessages*.properties"
],
"excludedSourceFilters":[
"**/*_zh.properties"
],
"xlfFolderPath":"./XLF",
"targetFolderPath":".",
"pseudoLocFolderPath":"."
}
]
},
{
"collectionName":"Polars",
"folders":[
{
"startingFolderPath":"java/com.sap.sailing.polars.datamining/resources/stringmessages",
"sourceFilters":[
"**/Polars_StringMessages*.properties"
],
"excludedSourceFilters":[
"**/*_zh.properties"
],
"xlfFolderPath":"./XLF",
"targetFolderPath":".",
"pseudoLocFolderPath":"."
}
]
},
{
"collectionName":"DataMining",
"folders":[
{
"startingFolderPath":"java/com.sap.sse.datamining/resources/stringmessages",
"sourceFilters":[
"**/StringMessages*.properties"
],
"excludedSourceFilters":[
"**/*_zh.properties"
],
"xlfFolderPath":"./XLF",
"targetFolderPath":".",
"pseudoLocFolderPath":"."
},
{
"startingFolderPath":"java/com.sap.sailing.datamining/resources/stringmessages",
"sourceFilters":[
"**/Sailing_StringMessages*.properties"
],
"excludedSourceFilters":[
"**/*_zh.properties"
],
"xlfFolderPath":"./XLF",
"targetFolderPath":".",
"pseudoLocFolderPath":"."
}
]
},
{
"collectionName":"FileStorage",
"folders":[
{
"startingFolderPath":"java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage",
"sourceFilters":[
"**/StringMessages*.properties"
],
"excludedSourceFilters":[
"**/*_zh.properties"
],
"xlfFolderPath":"./XLF",
"targetFolderPath":".",
"pseudoLocFolderPath":"."
},
{
"startingFolderPath":"java/com.sap.sse.filestorage/resources/stringmessages",
"sourceFilters":[
"**/FileStorageStringMessages*.properties"
],
"excludedSourceFilters":[
"**/*_zh.properties"
],
"xlfFolderPath":"./XLF",
"targetFolderPath":".",
"pseudoLocFolderPath":"."
}
]
},
{
"collectionName":"SecurityGwtUi",
"folders":[
{
"startingFolderPath":"java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n",
"sourceFilters":[
"**/StringMessages*.properties"
],
"excludedSourceFilters":[
"**/*_zh.properties"
],
"xlfFolderPath":"./XLF",
"targetFolderPath":".",
"pseudoLocFolderPath":"."
}
]
},
{
"collectionName":"SailingServer",
"folders":[
{
"startingFolderPath":"java/com.sap.sailing.server/resources/stringmessages",
"sourceFilters":[
"**/StringMessages*.properties"
],
"excludedSourceFilters":[
"**/*_zh.properties"
],
"xlfFolderPath":"./XLF",
"targetFolderPath":".",
"pseudoLocFolderPath":"."
}
]
}
],
"defaultConfiguration":{
"xlfFolderPath":"./XLF",
"targetFolderPath":".",
"pseudoLocFolderPath":".",
"oneQFolderPath":"/1Q/[fullPath]/[targetFile]",
"targetFileNamingConvention":"[filename]_[langCode].[extension]"
}
}