mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-19 12:15:28 +00:00
Merge branch 'master' into bug2447
This commit is contained in:
@@ -32,7 +32,6 @@
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="com.sap.sailing.dashboards.gwt"/> </runtimeClasspathEntry> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-servlet.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.moxieapps.gwt.highcharts/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt.adminconsole/src" path="3" type="2"/> "/>
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="com.sap.sailing.dashboards.gwt"/> </runtimeClasspathEntry> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-servlet.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.moxieapps.gwt.highcharts/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt.adminconsole/src" path="3" type="2"/> "/>
|
||||
|
||||
@@ -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
+46
@@ -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());
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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
|
||||
|
||||
+4
@@ -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);
|
||||
}
|
||||
|
||||
+6
@@ -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);
|
||||
}
|
||||
-1
@@ -140,5 +140,4 @@ public class BoatClassImpl extends NamedImpl implements BoatClass {
|
||||
public BoatHullType getHullType() {
|
||||
return hullType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+64
@@ -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());
|
||||
}
|
||||
}
|
||||
+8
@@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
+38
-9
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+37
-1
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
+3
-7
@@ -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>
|
||||
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
|
||||
+2
-1
@@ -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;
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
|
||||
+20
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+2
-1
@@ -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();
|
||||
}
|
||||
|
||||
+5
@@ -149,3 +149,8 @@
|
||||
.cellTableTotalColumn {
|
||||
background-color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
.cellTableDisabledRow {
|
||||
background: #e5e5e5;
|
||||
opacity : 0.8;
|
||||
}
|
||||
|
||||
+4
@@ -54,6 +54,10 @@ public interface AdminConsoleTableResources extends CellTable.Resources {
|
||||
*/
|
||||
String cellTableCheckboxDeselected();
|
||||
|
||||
/**
|
||||
* Applied to the disabled rows
|
||||
*/
|
||||
String cellTableDisabledRow();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+23
@@ -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 "";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+105
-6
@@ -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
|
||||
|
||||
+2
@@ -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
|
||||
|
||||
+1
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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> {
|
||||
|
||||
+2
@@ -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
|
||||
|
||||
+2
@@ -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
|
||||
|
||||
+2
@@ -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
|
||||
|
||||
+21
-11
@@ -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() {
|
||||
|
||||
+5
-3
@@ -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);
|
||||
|
||||
+2
-1
@@ -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());
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
BIN
Binary file not shown.
+29
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+71
@@ -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,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>
|
||||
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
|
||||
+12
@@ -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
|
||||
|
||||
+53
-24
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+22
@@ -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();
|
||||
}
|
||||
}
|
||||
+20
@@ -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;
|
||||
}
|
||||
}
|
||||
+52
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+24
@@ -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();
|
||||
}
|
||||
}
|
||||
+21
@@ -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;
|
||||
}
|
||||
}
|
||||
+78
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+32
-3
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-11
@@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
+237
-224
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+246
-236
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+31
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-2
@@ -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
|
||||
|
||||
+2
-3
@@ -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;
|
||||
}
|
||||
|
||||
+286
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
+32
@@ -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);
|
||||
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
+1
@@ -99,4 +99,5 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages {
|
||||
String error();
|
||||
String refresh();
|
||||
String preferredLanguage();
|
||||
String cannotResetInvalidURL();
|
||||
}
|
||||
|
||||
+1
@@ -95,3 +95,4 @@ accountConfirmation=Account confirmation
|
||||
error=Unknown error
|
||||
refresh=Refresh
|
||||
preferredLanguage=Preferred language
|
||||
cannotResetInvalidURL=Invalid reset URL...
|
||||
|
||||
+1
@@ -95,3 +95,4 @@ accountConfirmation=Kontobestätigung
|
||||
error=Es ist ein Fehler aufgetreten
|
||||
refresh=Aktualisieren
|
||||
preferredLanguage=Bevorzugte Sprache
|
||||
cannotResetInvalidURL="Fehlerhafte Reseturl ..."
|
||||
|
||||
+9
-8
@@ -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
|
||||
|
||||
@@ -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":"."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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]"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user